开机屏幕亮出现康佳符号后,就出现康佳电视初始化设置…屏幕暗了怎么办呢

我家的乐华电视怎么屏幕变小了推荐回答:一定要水平向前推,就是偏转线圈脱出了,把线圈向前推紧,图像就该是斜的了,否则可能被电击,显像管上套了一个线圈,拔掉电源依然有高压存在,小心夹断显像管),再拧紧固定螺丝即可(不可使劲拧,把固定线圈的螺丝拧松。这个故障自己就可以处理,如果线圈推的过程旋转了,打不死人。关掉电视机如果是过去那种很大的显像管电视机,拔掉电源线,打开电视机后盖。小心别碰到显像管上的高压帽,但吓人电视机上的按键锁住了,怎么办?推荐回答:一 1.乐华N21K8怎么解除童锁打开遥控器在最下面有一个空键就是工厂键,按一下既可进入维修状态,可找到童锁项,也可以按菜单+6483,3次既可进入!2..乐华100Hz机心万能密码是987;3.乐华N21K8解童锁,打开遥控器在最下面有一个空键就是工厂键,按下可以进入维修状态,可找到童锁项,也可以按菜单+6483,3次进入!4.乐华三菱+飞利浦机心万能密码是2442;5.乐华21A1童锁输入024,童锁自行消除二, 海尔海信东芝TB1238机心万能密码是;海尔童锁功能有如下几种解锁方式:1.用菜单键解锁:按“MENU”键(或“FUNC”键),选择功能显示菜单(系统设定菜单),在子菜单里有童锁一项,按P+/-来移动光标移动到“童锁”字样处,按V+/-键将童锁设置为“关”,即将童锁功能关闭;2.按屏显键解锁:此类机器童锁后屏幕上无锁定标志,但是按屏显键节目号是红色:此类机器的解锁方法很简单:按遥控器上的“DISPLAY”键(频道号显示键),持续3秒钟(或5秒钟)以上,屏幕显示的节目号由红色变成绿色即可解锁。3.密码解锁:按“LOCK”键或童锁键然后输入4位您设定的密码按P+/-来移动光标移动到“童锁”字样处,按V+/-键将童锁设置为“关”,即将童锁功能关闭。工厂的初始密码为0000。4.组合锁解锁同时按下遥控器的静音键和屏显键,屏幕显示的锁头(电视童锁功能)就可以解开,当电视的童锁功能打开后,电视的本机键与遥控器单键都被锁定,关机后再次开机仍保持童锁状态。5.1269万能码解锁如:CPU:LC863320A,小信号是LA76810的机型,用户输入密码锁后又把密码忘了,或用户先输入定时关机后再按密码锁,这时就可输入1269万能码即可解决.不同的电视机型号、不同的电路设计有不同的解锁方式三,长虹长虹A6机心的解锁方法是同时按遥控器上的“静音”键和“显示”键;长虹CN-12机心的解锁方法是同时按遥控器上的“F”键和“静音”键,或“F”键和“返回”键。长虹21K32解除童锁方法同时按遥控器上的“F”和“静音”键长虹G2136k解除童锁方法先按住F键不放,再按静音键就OK了长虹B2116童锁1 同时按下屏幕键和消音键,电视显示童锁符号,电视进入童锁状态。这时,除遥 控器电源开关和面板电源开关外,其余任何键都不起作用。2 设定童锁功能后,若按面板或遥控器开关键关机,再按电源开关键开机,屏幕显示童锁符号并且屏幕变黑。3 若要解除童锁状态(开锁),必须在屏幕显示童锁符号时,同时按下屏幕显示键 和消音键即可,此时屏幕童锁符号消失,屏幕恢复正常显示。长虹C2152童锁解除1:按住遥控的F键之后按静音键2:按住遥控的静音键之后按屏显键用K5系列的遥控板,如K5D,同时按静音和状态?,就可解除。此法如不行,最笨的办法就是开机后换存储器,百分之百排除。用K5D摇控器,同时按静音和屏显?就可解除长虹H2121K童锁:按静音+AV 或者静音+F 即可长虹B2117童锁同时按静音键和屏显键就可解开。四,康佳 如何解童锁康佳电视T2166A2开机时进入童锁解除(LA863328)按静音键一次,再按数字健9三次,这是康佳的万能解锁。康佳T5435彩电的解锁方法是:同时按面板上的“频道+”和“频道-”键;1) E机童锁:如出现左上角有三个钥匙点,同时按电视机上节目控制键“+、-”大约几秒钟后就可以自动解开;2) A机童锁:如出现两把锁,中间有三个(*、*、*),首先按遥控器上静音,然后再按“9、9、9”。3) P机童锁:先把电视机上的预先开关打开,再将“SPIK”键和“AV/TV”键同时按4,SG机童锁:连续按下“6,6,6,6,6”后方可解锁;五,夏华电视机万能解童锁用0870可解开任何密码的童锁!夏华E系列三洋单片机万能密码是4321;厦华电视TS2980怎么解童锁按住音乐键几钞直至屏幕右上角一把锁消失E系列: 通用密码是(4321);F系列: 通用密码是(0870);J系列: 通用密码是(5432);G295: 通用密码是(按住童锁4秒以上);注:出厂童锁密码设为"0000",没有万能密码,若童锁不慎自锁,可按住童锁键4秒以后,即可进入童锁菜单状态,将密码重设为"0000"。TF2955H系列:通用密码是(0398)。TS系列: 当您按住音乐键几秒直至屏幕右上角出现一把锁,屏幕为蓝屏,遥控器上多数按键将不起作用。如果您想解开童锁,按住音乐键几钞直至屏幕右上角一把锁消失六,创维解童锁 的解除方法现在的机器童锁功能有如下几种解锁方式:1.用菜单键解锁:按“MENU”键(或“FUNC”键),选择功能显示菜单(系统设定菜单),在子菜单里有童锁一项,按P+/-来移动光标移动到“童锁”字样处,按V+/-键将童锁设置为“关”,即将童锁功能关闭;2.按屏显键解锁:此类机器童锁后屏幕上无锁定标志,但是按屏显键节目号是红色:此类机器的解锁方法很简单:按遥控器上的“DISPLAY”键(频道号显示键),持续3秒钟(或5秒钟)以上,屏幕显示的节目号由红色变成绿色即可解锁。3.密码解锁:按“LOCK”键或童锁键然后输入4位您设定的密码按P+/-来移动光标移动到“童锁”字样处,按V+/-键将童锁设置为“关”,即将童锁功能关闭。工厂的初始密码为0000。4.组合锁解锁 同时按下遥控器的静音键和屏显键,屏幕显示的锁头(电视童锁功能)就可以解开,当电视的童锁功能打开后,电视的本机键与遥控器单键都被锁定,关机后再次开机仍保持童锁状态。不同的电视机型号、不同的电路设计有不同的解锁方式创维A万能遥控试密码,打了107后遥控恢复正常七,TCLTCL彩电AT21106的童锁解除方法开机后按住主机的“音量▼”键,使音量减到0,同时按遥控器上的“0”键三下。进入工厂菜单模式后,直接按遥控器上的数字键查看相应的工厂菜单,再配合遥控器上的“音量▲”和“音量▼”、“节目▲”和“节目▼”键对每个项目进行必要的调节。八,杂牌机及其它1,杂牌机万能童锁解除方法:长按屏显键或静音健,召回键几秒2,杂牌机万能童锁解除方法:断开存储块的第8脚,开机,在不断电的情况下再焊上,既可打开童锁使用M3童锁解除方法如果菜单中童锁一项为灰色说明无法用遥控器消除,我的方法是关机脱开存储器非接地脚开机再焊上即可消除请注意在开机状态下焊同时还要查按键是否漏电。注:一般电视机的童锁在该机的说明书上都专门有说明。使用lcs3 lc童锁解除方法按屏显键几秒就解杂牌机MFP童锁按住面板菜单键不放,另一手指按住频道+或频道- 或音量+ -键5秒钟之上即可高路华2518不能解除童锁按遥控F键3秒后屏幕显示SP--输入01后按机上存储键高路华,cpu是T87CK38N-1V50同时按住遥控器的 静音和屏显即可上广电D2905F童锁按遥控器黄色的按钮5秒LG CD-21A92R的童锁LG的电视童锁控制在菜单里,用遥控器进入菜单找到童锁一项关闭北京2506G童锁按屏显键3秒以上三星CS—7277P彩电1.按MENU键,屏幕显示菜单。2。按压CH键 ,使箭头处于功能位置。3。按压音量键,使箭头进入功能菜单。4。按压CH 键,使箭头处于频道童锁位置。5。按压音量键,箭头进入频道童锁检查,屏幕显示:台号:P00是否锁,关。6。按压音量+,台号从0开始向大的方向 变化。如果发现某一台号童锁为开,则按压CH-键,使是否锁的字符变为红色,然后按压音量键,将开变为关。7。继续用同样的方法将0-99频道都检查一遍,确认无童锁开为止。最后按压黄色MENU键,退出功能状态嘉华21w3f童锁按着OK键不放再按开机键既可飞利浦21PT1582/93童锁连续输入两次0711按确认即可如果实在不能解开童锁,则换写有原装数据的存贮器海信东芝TB1238机心万能密码是;乐华100Hz机心万能密码是987;乐华三菱+飞利浦机心万能密码是2442;新买的乐华液晶电视开机屏幕不显示推荐回答:乐华液晶电视开机屏幕不显示的原因和解决方法:1、电视机未通电导致屏幕不显示,可以检查电视机是否接通了电源以及是否开启了电源开关,确保电视机接通电源且打开了电源开关才能正常开机。2、电视机处于待机状态导致屏幕不显示,一般可以按一下遥控器的待机键来唤醒电视机,使电视机显示出图像。3、如果经过以上方法处理后仍然不显示,则可能是电视机的硬件故障导致没有反应,具体包括电视机开关电路故障(短路或开路)、电源模块故障、屏幕或排线故障、主板及其他零部件故障等。这种情况需要联系电视机的售后服务对电视机进行全面的检修或者直接申请退换货解决。乐华v59电视万能驱动板连接笔记本屏幕出现这种画面是什么问题该如何解决啊?????推荐回答:到论坛再下个驱动吧用U盘多试几个就好了,找不到固件可以联系我我发给你如何将乐华液晶电视连接在惠普笔记本电脑上当显示呢?请回答推荐回答:将电视机与电脑连接,使用视频串口连接线。2在电脑桌面空白处右键菜单中选择“属性”→“设置”→“高级”→进行双显示模式设置。3将电视机选择有关电脑信号输入即可,就是连接显示器的那种乐华电视按着恢复出厂设置后关不了机,拨电原关机重启屏幕两边分别出现智能系统启动中,一直保持不变,也推荐回答:恢复出场设置需要一段时间乐华电视机液晶显示屏碎了怎么办推荐回答:如果不是残次屏价格基本上都差不多。4:1,如果你确实觉得可惜有2个选择,3千更换一个残次屏。 作为一个行内的人,你也不知道他会什么时候坏。 当然,本身效果也不会多理想、但是更换屏后不会重新质保,如果我自己的遇到这种情况我会选择放弃重新购买 首先同情一下你的遭遇。2、目前能做大屏的厂家就那么几家,一般厂家提供1到3个月保修:一个是找一个小品牌售后服务站帮你找一块屏、原装屏价格基本上约等于一台新机价格。 原因、如果花2,不过要私下谈,加上利润即使不要5200。3,还有一个选择是把你手里的坏机子处理给售后服务站,4000左右还是要的,人为损坏屏确实是个很麻烦的事情乐华电视屏幕出现“rowa”怎么办?推荐回答:-,打火严重的伴有异味等,时间长了高压包处就会因潮湿而打火!说液晶屏坏了是不负责任的;符号,打火会在屏幕上引起雪花点.. INPUT键你先试试遥控开机在重开.乐华 N21K3数据在如下地址下载,原因为高压包处受潮。这是显象管高压包处打火所致,不行的话就拆开遥控器看看里面有没有隐藏按键,那么接着再按直到所有字符消失,跟本不懂装懂屏是没坏应是驱动板有问题已进入工厂调整模式 如果遥控关机就可退出 如不能退出 可以在百度搜索一下《乐华XXX型号如何进入总线》可查到退出方法。如果按到某一键字符出现变化。 祝愉快,如果有就逐个试试看按下有无反映.。切忌:在出现有数字的字符时千万不要按音量加减键 还是不行请发上电视机型;&quot.; 有一个独立键有"AV键直至见到电视画面为止重按TV/,建议请专业人士对高压包处进行清洗和处理展开全部下一篇:如今电视已经走进千家万户,那么大家对于排在十大品牌之一的乐华电视了解多少呢?乐华电视是1982年创立的电视品牌,是全国领先的电视品牌。企业以创一流品牌,走实用消费之道,做中国消费电子行业最具竞争力的品牌”为宗旨。乐华液晶电视怎么样?恐怕每个人的答案都会不一样。那么,接下来就让我们对乐华液晶电视做一个具体的了解吧!现在国产电视做的越来越好,在价格方面还有优势,所以关注的人也就越来越多,大家知道乐华液晶电视吗?今天我们就来为大家介绍一下,请阅读下文。夏华电视故障之液晶屏故障对于一台电视来讲,屏幕故障是经常会出现的,一般的屏幕故障主要包括屏裂,还有屏暗。随着科技的发展,人们的生活越来越方便,现在很多的电视都可以实现多种功能,很多的家庭在选购电视的时候会选择乐视电视。那么,乐视电视怎么样呢?乐视电视的发展起源于互联网电视的热门,电视的资源是用户选择电视时最关注的问题,乐视视频拥有很多的高清资源,最新的影视都可以观看。乐视电视s50是乐视品牌下的电视产品中,公认的比较具有经典性和代表意义的一款电视型号,因为乐视电视s50具备了比较高的分辨率、近乎完美的画质乐视电视是目前比较知名的电视品牌之一,并且每年都会推出一系列的“超级电视”,用来满足消费者们日益精进的观影需求。乐视电视是非常受年轻人们欢迎和喜爱的一个新兴的国产电视品牌,尤其是喜欢网络电视的人们对于乐视电视更是情有独钟的。合作伙伴企业版售前咨询(08:30-17:30)业主服务号设计师服务号热门标签康佳液晶电视显示屏上的音量符号总是在屏幕上,怎么才能消除掉_百度知道
康佳液晶电视显示屏上的音量符号总是在屏幕上,怎么才能消除掉
我有更好的答案
你修好了吗?怎么解决的?我家也是这种情况
采纳率:100%
尊敬的康佳用户:您好,很抱歉,产品使用给您带来不便了!根据您的描述,建议您重启电视或者将电视机原装遥控器取下来尝试,若问题仍然如此,您可以登陆康佳官网咨询,感谢您的咨询,祝您生活愉快!
本回答被网友采纳
如果遥控器可以正常调音量,拔掉电视机电源重启电视就可以
2条折叠回答
为您推荐:
其他类似问题
康佳液晶电视的相关知识
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。周热销排行
用户评论(0)
在此可输入您对该资料的评论~
添加成功至
资料评价:随笔 - 2029&
文章 - 1&评论 - 147&trackbacks - 0
好几个月都没有更新过博客了,从今天开始,老罗将尝试对Android系统的UI实现作一个系统的分析,也算是落实之前所作出的承诺。提到Android系统的UI,我们最先接触到的便是系统在启动过程中所出现的画面了。Android系统在启动的过程中,最多可以出现三个画面,每一个画面都用来描述一个不同的启动阶段。本文将详细分析这三个开机画面的显示过程,以便可以开启我们对Android系统UI实现的分析之路。
&& & & &第一个开机画面是在内核启动的过程中出现的,它是一个静态的画面。第二个开机画面是在init进程启动的过程中出现的,它也是一个静态的画面。第三个开机画面是在系统服务启动的过程中出现的,它是一个动态的画面。无论是哪一个画面,它们都是在一个称为帧缓冲区(frame buffer,简称fb)的硬件设备上进行渲染的。接下来,我们就分别分析这三个画面是如何在fb上显示的。
&& & & &1. 第一个开机画面的显示过程
&& & & &Android系统的第一个开机画面其实是Linux内核的启动画面。在默认情况下,这个画面是不会出现的,除非我们在编译内核的时候,启用以下两个编译选项:
&& & & &CONFIG_FRAMEBUFFER_CONSOLE
&& & & &CONFIG_LOGO
&& & & &第一个编译选项表示内核支持帧缓冲区控制台,它对应的配置菜单项为:Device Drivers ---& Graphics support ---& Console display driver support ---& Framebuffer Console support。第二个编译选项表示内核在启动的过程中,需要显示LOGO,它对应的配置菜单项为:Device Drivers ---& Graphics support ---& Bootup logo。配置Android内核编译选项可以参考一文。
&& & & &帧缓冲区硬件设备在内核中有一个对应的驱动程序模块fbmem,它实现在文件kernel/goldfish/drivers/video/fbmem.c中,它的初始化函数如下所示:
static&int&__init&&
fbmem_init(void)&&
&&&&&&&&proc_create("fb",&0,&NULL,&&fb_proc_fops);&&
&&&&&&&&if&(register_chrdev(FB_MAJOR,"fb",&fb_fops))&&
&&&&&&&&&&&&&&&&printk("unable&to&get&major&%d&for&fb&devs\n",&FB_MAJOR);&&
&&&&&&&&fb_class&=&class_create(THIS_MODULE,&"graphics");&&
&&&&&&&&if&(IS_ERR(fb_class))&{&&
&&&&&&&&&&&&&&&&printk(KERN_WARNING&"Unable&to&create&fb&&errno&=&%ld\n",&PTR_ERR(fb_class));&&
&&&&&&&&&&&&&&&&fb_class&=&NULL;&&
&&&&&&&&}&&
&&&&&&&&return&0;&&
&& & & &这个函数首先调用函数proc_create在/proc目录下创建了一个fb文件,接着又调用函数register_chrdev来注册了一个名称为fb的字符设备,最后调用函数class_create在/sys/class目录下创建了一个graphics目录,用来描述内核的图形系统。
&& & & &模块fbmem除了会执行上述初始化工作之外,还会导出一个函数register_framebuffer:
EXPORT_SYMBOL(register_framebuffer);&&
&& & & &这个函数在内核的启动过程会被调用,以便用来执行注册帧缓冲区硬件设备的操作,它的实现如下所示:
register_framebuffer(struct&fb_info&*fb_info)&&
&&&&&&&&int&i;&&
&&&&&&&&struct&fb_event&&&
&&&&&&&&......&&
&&&&&&&&if&(num_registered_fb&==&FB_MAX)&&
&&&&&&&&&&&&&&&&return&-ENXIO;&&
&&&&&&&&......&&
&&&&&&&&num_registered_fb++;&&
&&&&&&&&for&(i&=&0&;&i&&&FB_MAX;&i++)&&
&&&&&&&&&&&&&&&&if&(!registered_fb[i])&&
&&&&&&&&&&&&&&&&&&&&&&&&break;&&
&&&&&&&&fb_info-&node&=&i;&&
&&&&&&&&mutex_init(&fb_info-&lock);&&
&&&&&&&&fb_info-&dev&=&device_create(fb_class,&fb_info-&device,&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&MKDEV(FB_MAJOR,&i),&NULL,&"fb%d",&i);&&
&&&&&&&&if&(IS_ERR(fb_info-&dev))&{&&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&printk(KERN_WARNING&"Unable&to&create&device&for&framebuffer&%d;&errno&=&%ld\n",&i,&PTR_ERR(fb_info-&dev));&&
&&&&&&&&&&&&&&&&fb_info-&dev&=&NULL;&&
&&&&&&&&}&else&&
&&&&&&&&&&&&&&&&fb_init_device(fb_info);&&
&&&&&&&&......&&
&&&&&&&&registered_fb[i]&=&fb_&&
&&&&&&&&event.info&=&fb_&&
&&&&&&&&fb_notifier_call_chain(FB_EVENT_FB_REGISTERED,&&event);&&
&&&&&&&&return&0;&&
&& & & &由于系统中可能会存在多个帧缓冲区硬件设备,因此,fbmem模块使用一个数组registered_fb保存所有已经注册了的帧缓冲区硬件设备,其中,每一个帧缓冲区硬件都是使用一个结构体fb_info来描述的。
&& & & &我们知道,在Linux内核中,每一个硬件设备都有一个主设备号和一个从设备号,它们用来唯一地标识一个硬件设备。对于帧缓冲区硬件设备来说,它们的主设备号定义为FB_MAJOR(29),而从设备号则与注册的顺序有关,它们的值依次等于0,1,2等。
&& & & &每一个被注册的帧缓冲区硬件设备在/dev/graphics目录下都有一个对应的设备文件fb&minor&,其中,&minor&表示一个从设备号。例如,第一个被注册的帧缓冲区硬件设备在/dev/graphics目录下都有一个对应的设备文件fb0。用户空间的应用程序通过这个设备文件就可以操作帧缓冲区硬件设备了,即将要显示的画面渲染到帧缓冲区硬件设备上去。
&& & & &这个函数最后会通过调用函数fb_notifier_call_chain来通知帧缓冲区控制台,有一个新的帧缓冲区设备被注册到内核中来了。
&& & & &帧缓冲区控制台在内核中对应的驱动程序模块为fbcon,它实现在文件kernel/goldfish/drivers/video/console/fbcon.c中,它的初始化函数如下所示:
static&struct&notifier_block&fbcon_event_notifier&=&{&&
&&&&&&&&.notifier_call&&=&fbcon_event_notify,&&
static&int&__init&fb_console_init(void)&&
&&&&&&&&int&i;&&
&&&&&&&&acquire_console_sem();&&
&&&&&&&&fb_register_client(&fbcon_event_notifier);&&
&&&&&&&&fbcon_device&=&device_create(fb_class,&NULL,&MKDEV(0,&0),&NULL,&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"fbcon");&&
&&&&&&&&if&(IS_ERR(fbcon_device))&{&&
&&&&&&&&&&&&&&&&printk(KERN_WARNING&"Unable&to&create&device&"&&
&&&&&&&&&&&&&&&&&&&&&&&"for&&errno&=&%ld\n",&&
&&&&&&&&&&&&&&&&&&&&&&&PTR_ERR(fbcon_device));&&
&&&&&&&&&&&&&&&&fbcon_device&=&NULL;&&
&&&&&&&&}&else&&
&&&&&&&&&&&&&&&&fbcon_init_device();&&
&&&&&&&&for&(i&=&0;&i&&&MAX_NR_CONSOLES;&i++)&&
&&&&&&&&&&&&&&&&con2fb_map[i]&=&-1;&&
&&&&&&&&release_console_sem();&&
&&&&&&&&fbcon_start();&&
&&&&&&&&return&0;&&
&& & & &这个函数除了会调用函数device_create来创建一个类别为graphics的设备fbcon之外,还会调用函数fb_register_client来监听帧缓冲区硬件设备的注册事件,这是由函数fbcon_event_notify来实现的,如下所示:
static&int&fbcon_event_notify(struct&notifier_block&*self,&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&unsigned&long&action,&void&*data)&&
&&&&&&&&struct&fb_event&*event&=&&&
&&&&&&&&struct&fb_info&*info&=&event-&&&
&&&&&&&&......&&
&&&&&&&&int&ret&=&0;&&
&&&&&&&&......&&
&&&&&&&&switch(action)&{&&
&&&&&&&&......&&
&&&&&&&&case&FB_EVENT_FB_REGISTERED:&&
&&&&&&&&&&&&&&&&ret&=&fbcon_fb_registered(info);&&
&&&&&&&&&&&&&&&&break;&&
&&&&&&&&......&&
&&&&&&&&}&&
&&&&&&&&return&&&
&& & & &帧缓冲区硬件设备的注册事件最终是由函数fbcon_fb_registered来处理的,它的实现如下所示:
static&int&fbcon_fb_registered(struct&fb_info&*info)&&
&&&&&&&&int&ret&=&0,&i,&idx&=&info-&&&
&&&&&&&&fbcon_select_primary(info);&&
&&&&&&&&if&(info_idx&==&-1)&{&&
&&&&&&&&&&&&&&&&for&(i&=&first_fb_&i&&=&last_fb_&i++)&{&&
&&&&&&&&&&&&&&&&&&&&&&&&if&(con2fb_map_boot[i]&==&idx)&{&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&info_idx&=&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&break;&&
&&&&&&&&&&&&&&&&&&&&&&&&}&&
&&&&&&&&&&&&&&&&}&&
&&&&&&&&&&&&&&&&if&(info_idx&!=&-1)&&
&&&&&&&&&&&&&&&&&&&&&&&&ret&=&fbcon_takeover(1);&&
&&&&&&&&}&else&{&&
&&&&&&&&&&&&&&&&for&(i&=&first_fb_&i&&=&last_fb_&i++)&{&&
&&&&&&&&&&&&&&&&&&&&&&&&if&(con2fb_map_boot[i]&==&idx)&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&set_con2fb_map(i,&idx,&0);&&
&&&&&&&&&&&&&&&&}&&
&&&&&&&&}&&
&&&&&&&&return&&&
&& & & &函数fbcon_select_primary用来检查当前注册的帧缓冲区硬件设备是否是一个主帧缓冲区硬件设备。如果是的话,那么就将它的信息记录下来。这个函数只有当指定了CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY编译选项时才有效,否则的话,它是一个空函数。
&& & & &在Linux内核中,每一个控制台和每一个帧缓冲区硬件设备都有一个从0开始的编号,它们的初始对应关系保存在全局数组con2fb_map_boot中。控制台和帧缓冲区硬件设备的初始对应关系是可以通过设置内核启动参数来初始化的。在模块fbcon中,还有另外一个全局数组con2fb_map,也是用来映射控制台和帧缓冲区硬件设备的对应关系,不过它映射的是控制台和帧缓冲区硬件设备的实际对应关系。
&& & & &全局变量first_fb_vc和last_fb_vc是全局数组con2fb_map_boot和con2fb_map的索引值,用来指定系统当前可用的控制台编号范围,它们也是可以通过设置内核启动参数来初始化的。全局变量first_fb_vc的默认值等于0,而全局变量last_fb_vc的默认值等于MAX_NR_CONSOLES - 1。
&& & & &全局变量info_idx表示系统当前所使用的帧缓冲区硬件的编号。如果它的值等于-1,那么就说明系统当前还没有设置好当前所使用的帧缓冲区硬件设备。在这种情况下,函数fbcon_fb_registered就会在全局数组con2fb_map_boot中检查是否存在一个控制台编号与当前所注册的帧缓冲区硬件设备的编号idx对应。如果存在的话,那么就会将当前所注册的帧缓冲区硬件设备编号idx保存在全局变量info_idx中。接下来还会调用函数fbcon_takeover来初始化系统所使用的控制台。在调用函数fbcon_takeover的时候,传进去的参数为1,表示要显示第一个开机画面。
&& & & &如果全局变量info_idx的值不等于-1,那么函数fbcon_fb_registered同样会在全局数组con2fb_map_boot中检查是否存在一个控制台编号与当前所注册的帧缓冲区硬件设备的编号idx对应。如果存在的话,那么就会调用函数set_con2fb_map来调整当前所注册的帧缓冲区硬件设备与控制台的映射关系,即调整数组con2fb_map_boot和con2fb_map的值。
&& & & &为了简单起见,我们假设系统只有一个帧缓冲区硬件设备,这样当它被注册的时候,全局变量info_idx的值就会等于-1。当函数fbcon_fb_registered在全局数组con2fb_map_boot中发现有一个控制台的编号与这个帧缓冲区硬件设备的编号idx对应时,接下来就会调用函数fbcon_takeover来设置系统所使用的控制台。
&& & & &函数fbcon_takeover的实现如下所示:
static&int&fbcon_takeover(int&show_logo)&&
&&&&&&&&int&err,&i;&&
&&&&&&&&if&(!num_registered_fb)&&
&&&&&&&&&&&&&&&&return&-ENODEV;&&
&&&&&&&&if&(!show_logo)&&
&&&&&&&&&&&&&&&&logo_shown&=&FBCON_LOGO_DONTSHOW;&&
&&&&&&&&for&(i&=&first_fb_&i&&=&last_fb_&i++)&&
&&&&&&&&&&&&&&&&con2fb_map[i]&=&info_&&
&&&&&&&&err&=&take_over_console(&fb_con,&first_fb_vc,&last_fb_vc,&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&fbcon_is_default);&&
&&&&&&&&if&(err)&{&&
&&&&&&&&&&&&&&&&for&(i&=&first_fb_&i&&=&last_fb_&i++)&{&&
&&&&&&&&&&&&&&&&&&&&&&&&con2fb_map[i]&=&-1;&&
&&&&&&&&&&&&&&&&}&&
&&&&&&&&&&&&&&&&info_idx&=&-1;&&
&&&&&&&&}&&
&&&&&&&&return&&&
&& & & &全局变量logo_shown的初始值为FBCON_LOGO_CANSHOW,表示可以显示第一个开机画面。但是当参数show_logo的值等于0的时候,全局变量logo_shown的值会被重新设置为FBCON_LOGO_DONTSHOW,表示不可以显示第一个开机画面。
&& & & &中间的for循环将当前可用的控制台的编号都映射到当前正在注册的帧缓冲区硬件设备的编号info_idx中去,表示当前可用的控制台与缓冲区硬件设备的实际映射关系。
&& & & &函数take_over_console用来初始化系统当前所使用的控制台。如果它的返回值不等于0,那么就表示初始化失败。在这种情况下,最后的for循环就会将全局数组con2fb_map的各个元素的值设置为-1,表示系统当前可用的控制台还没有映射到实际的帧缓冲区硬件设备中去。这时候全局变量info_idx的值也会被重新设置为-1。
&& & & 调用函数take_over_console来初始化系统当前所使用的控制台,实际上就是向系统注册一系列回调函数,以便系统可以通过这些回调函数来操作当前所使用的控制台。这些回调函数使用结构体consw来描述。这里所注册的结构体consw是由全局变量fb_con来指定的,它的定义如下所示:
static&const&struct&consw&fb_con&=&{&&
&&&&&&&&.owner&&&&&&&&&&&&&&&&&&=&THIS_MODULE,&&
&&&&&&&&.con_startup&&&&&&&&&&&&=&fbcon_startup,&&
&&&&&&&&.con_init&&&&&&&&&&&&&&&=&fbcon_init,&&
&&&&&&&&.con_deinit&&&&&&&&&&&&&=&fbcon_deinit,&&
&&&&&&&&.con_clear&&&&&&&&&&&&&&=&fbcon_clear,&&
&&&&&&&&.con_putc&&&&&&&&&&&&&&&=&fbcon_putc,&&
&&&&&&&&.con_putcs&&&&&&&&&&&&&&=&fbcon_putcs,&&
&&&&&&&&.con_cursor&&&&&&&&&&&&&=&fbcon_cursor,&&
&&&&&&&&.con_scroll&&&&&&&&&&&&&=&fbcon_scroll,&&
&&&&&&&&.con_bmove&&&&&&&&&&&&&&=&fbcon_bmove,&&
&&&&&&&&.con_switch&&&&&&&&&&&&&=&fbcon_switch,&&
&&&&&&&&.con_blank&&&&&&&&&&&&&&=&fbcon_blank,&&
&&&&&&&&.con_font_set&&&&&&&&&&&=&fbcon_set_font,&&
&&&&&&&&.con_font_get&&&&&&&&&&&=&fbcon_get_font,&&
&&&&&&&&.con_font_default&&&&&&&=&fbcon_set_def_font,&&
&&&&&&&&.con_font_copy&&&&&&&&&&=&fbcon_copy_font,&&
&&&&&&&&.con_set_palette&&&&&&&&=&fbcon_set_palette,&&
&&&&&&&&.con_scrolldelta&&&&&&&&=&fbcon_scrolldelta,&&
&&&&&&&&.con_set_origin&&&&&&&&&=&fbcon_set_origin,&&
&&&&&&&&.con_invert_region&&&&&&=&fbcon_invert_region,&&
&&&&&&&&.con_screen_pos&&&&&&&&&=&fbcon_screen_pos,&&
&&&&&&&&.con_getxy&&&&&&&&&&&&&&=&fbcon_getxy,&&
&&&&&&&&.con_resize&&&&&&&&&&&&&=&fbcon_resize,&&
&& & & 接下来我们主要关注函数fbcon_init和fbcon_switch的实现,系统就是通过它来初始化和切换控制台的。在初始化的过程中,会决定是否需要准备第一个开机画面的内容,而在切换控制台的过程中,会决定是否需要显示第一个开机画面的内容。
&& & & 函数fbcon_init的实现如下所示:
static&void&fbcon_init(struct&vc_data&*vc,&int&init)&&
&&&&&&&&struct&fb_info&*info&=&registered_fb[con2fb_map[vc-&vc_num]];&&
&&&&&&&&struct&fbcon_ops&*&&
&&&&&&&&struct&vc_data&**default_mode&=&vc-&vc_display_&&
&&&&&&&&struct&vc_data&*svc&=&*default_&&
&&&&&&&&struct&display&*t,&*p&=&&fb_display[vc-&vc_num];&&
&&&&&&&&int&logo&=&1,&new_rows,&new_cols,&rows,&cols,&charcnt&=&256;&&
&&&&&&&&int&&&
&&&&&&&&if&(info_idx&==&-1&||&info&==&NULL)&&
&&&&&&&&&&&&return;&&
&&&&&&&&......&&
&&&&&&&&if&(vc&!=&svc&||&logo_shown&==&FBCON_LOGO_DONTSHOW&||&&
&&&&&&&&&&&&(info-&fix.type&==&FB_TYPE_TEXT))&&
&&&&&&&&&&&&&&&&logo&=&0;&&
&&&&&&&&......&&
&&&&&&&&if&(logo)&&
&&&&&&&&&&&&&&&&fbcon_prepare_logo(vc,&info,&cols,&rows,&new_cols,&new_rows);&&
&&&&&&&&......&&
&& & & &当前正在初始化的控制台使用参数vc来描述,而它的成员变量vc_num用来描述当前正在初始化的控制台的编号。通过这个编号之后,就可以在全局数组con2fb_map中找到对应的帧缓冲区硬件设备编号。有了帧缓冲区硬件设备编号之后,就可以在另外一个全局数组中registered_fb中找到一个fb_info结构体info,用来描述与当前正在初始化的控制台所对应的帧缓冲区硬件设备。
&& & & &参数vc的成员变量vc_display_fg用来描述系统当前可见的控制台,它是一个类型为vc_data**的指针。从这里就可以看出,最终得到的vc_data结构体svc就是用来描述系统当前可见的控制台的。
&& & & &变量logo开始的时候被设置为1,表示需要显示第一个开机画面,但是在以下三种情况下,它的值会被设置为0,表示不需要显示开机画面:
&& & & &A. 参数vc和变量svc指向的不是同一个vc_data结构体,即当前正在初始化的控制台不是系统当前可见的控制台。
&& & & &B. 全局变量logo_shown的值等于FBCON_LOGO_DONTSHOW,即系统不需要显示第一个开机画面。
&& & & &C. 与当前正在初始化的控制台所对应的帧缓冲区硬件设备的显示方式被设置为文本方式,即info-&fix.type的值等于FB_TYPE_TEXT。
&& & & &当最终得到的变量logo的值等于1的时候,接下来就会调用函数fbcon_prepare_logo来准备要显示的第一个开机画面的内容。
&& & & &在函数fbcon_prepare_logo中,第一个开机画面的内容是通过调用函数fb_prepare_logo来准备的,如下所示:
static&void&fbcon_prepare_logo(struct&vc_data&*vc,&struct&fb_info&*info,&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&int&cols,&int&rows,&int&new_cols,&int&new_rows)&&
&&&&&&&&......&&
&&&&&&&&int&logo_&&
&&&&&&&&......&&
&&&&&&&&logo_height&=&fb_prepare_logo(info,&ops-&rotate);&&
&&&&&&&&&&
&&&&&&&&......&&
&&&&&&&&if&(logo_lines&&&vc-&vc_bottom)&{&&
&&&&&&&&&&&&&&&&......&&
&&&&&&&&}&else&if&(logo_shown&!=&FBCON_LOGO_DONTSHOW)&{&&
&&&&&&&&&&&&&&&&logo_shown&=&FBCON_LOGO_DRAW;&&
&&&&&&&&&&&&&&&&......&&
&&&&&&&&}&&
&& & & & 从函数fb_prepare_logo返回来之后,如果要显示的第一个开机画面所占用的控制台行数小于等于参数vc所描述的控制台的最大行数,并且全局变量logo_show的值不等于FBCON_LOGO_DONTSHOW,那么就说明前面所提到的第一个开机画面可以显示在控制台中。这时候全局变量logo_show的值就会被设置为FBCON_LOGO_DRAW,表示第一个开机画面处于等待渲染的状态。
&& & & & 函数fb_prepare_logo实现在文件kernel/goldfish/drivers/video/fbmem.c中,如下所示:
int&fb_prepare_logo(struct&fb_info&*info,&int&rotate)&&
&&&&&&&&int&depth&=&fb_get_color_depth(&info-&var,&&info-&fix);&&
&&&&&&&&unsigned&int&&&
&&&&&&&&memset(&fb_logo,&0,&sizeof(struct&logo_data));&&
&&&&&&&&......&&
&&&&&&&&if&(info-&fix.visual&==&FB_VISUAL_DIRECTCOLOR)&{&&
&&&&&&&&&&&&&&&&depth&=&info-&var.blue.&&
&&&&&&&&&&&&&&&&if&(info-&var.red.length&&&depth)&&
&&&&&&&&&&&&&&&&&&&&&&&&depth&=&info-&var.red.&&
&&&&&&&&&&&&&&&&if&(info-&var.green.length&&&depth)&&
&&&&&&&&&&&&&&&&&&&&&&&&depth&=&info-&var.green.&&
&&&&&&&&}&&
&&&&&&&&if&(info-&fix.visual&==&FB_VISUAL_STATIC_PSEUDOCOLOR&&&&depth&&&4)&{&&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&depth&=&4;&&
&&&&&&&&}&&
&&&&&&&&&&
&&&&&&&&fb_logo.logo&=&fb_find_logo(depth);&&
&&&&&&&&......&&
&&&&&&&&return&fb_prepare_extra_logos(info,&fb_logo.logo-&height,&yres);&&
&& & & &这个函数首先得到参数info所描述的帧缓冲区硬件设备的颜色深度depth,接着再调用函数fb_find_logo来获得要显示的第一个开机画面的内容,并且保存在全局变量fb_logo的成员变量logo中。
&& & & &函数fb_find_logo实现在文件kernel/goldfish/drivers/video/logo/logo.c文件中,如下所示:
extern&const&struct&linux_logo&logo_linux_&&
extern&const&struct&linux_logo&logo_linux_vga16;&&
extern&const&struct&linux_logo&logo_linux_clut224;&&
extern&const&struct&linux_logo&logo_blackfin_vga16;&&
extern&const&struct&linux_logo&logo_blackfin_clut224;&&
extern&const&struct&linux_logo&logo_dec_clut224;&&
extern&const&struct&linux_logo&logo_mac_clut224;&&
extern&const&struct&linux_logo&logo_parisc_clut224;&&
extern&const&struct&linux_logo&logo_sgi_clut224;&&
extern&const&struct&linux_logo&logo_sun_clut224;&&
extern&const&struct&linux_logo&logo_superh_&&
extern&const&struct&linux_logo&logo_superh_vga16;&&
extern&const&struct&linux_logo&logo_superh_clut224;&&
extern&const&struct&linux_logo&logo_m32r_clut224;&&
static&int&&&
module_param(nologo,&bool,&0);&&
MODULE_PARM_DESC(nologo,&"Disables&startup&logo");&&
const&struct&linux_logo&*&__init_refok&fb_find_logo(int&depth)&&
&&&&&&&&const&struct&linux_logo&*logo&=&NULL;&&
&&&&&&&&if&(nologo)&&
&&&&&&&&&&&&&&&&return&NULL;&&
&&&&&&&&if&(depth&&=&1)&{&&
#ifdef&CONFIG_LOGO_LINUX_MONO&&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&logo&=&&logo_linux_&&
#ifdef&CONFIG_LOGO_SUPERH_MONO&&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&logo&=&&logo_superh_&&
&&&&&&&&}&&
&&&&&&&&if&(depth&&=&4)&{&&
#ifdef&CONFIG_LOGO_LINUX_VGA16&&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&logo&=&&logo_linux_vga16;&&
#ifdef&CONFIG_LOGO_BLACKFIN_VGA16&&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&logo&=&&logo_blackfin_vga16;&&
#ifdef&CONFIG_LOGO_SUPERH_VGA16&&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&logo&=&&logo_superh_vga16;&&
&&&&&&&&}&&
&&&&&&&&if&(depth&&=&8)&{&&
#ifdef&CONFIG_LOGO_LINUX_CLUT224&&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&logo&=&&logo_linux_clut224;&&
#ifdef&CONFIG_LOGO_BLACKFIN_CLUT224&&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&logo&=&&logo_blackfin_clut224;&&
#ifdef&CONFIG_LOGO_DEC_CLUT224&&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&logo&=&&logo_dec_clut224;&&
#ifdef&CONFIG_LOGO_MAC_CLUT224&&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&if&(MACH_IS_MAC)&&
&&&&&&&&&&&&&&&&&&&&&&&&logo&=&&logo_mac_clut224;&&
#ifdef&CONFIG_LOGO_PARISC_CLUT224&&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&logo&=&&logo_parisc_clut224;&&
#ifdef&CONFIG_LOGO_SGI_CLUT224&&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&logo&=&&logo_sgi_clut224;&&
#ifdef&CONFIG_LOGO_SUN_CLUT224&&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&logo&=&&logo_sun_clut224;&&
#ifdef&CONFIG_LOGO_SUPERH_CLUT224&&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&logo&=&&logo_superh_clut224;&&
#ifdef&CONFIG_LOGO_M32R_CLUT224&&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&logo&=&&logo_m32r_clut224;&&
&&&&&&&&}&&
&&&&&&&&return&&&
EXPORT_SYMBOL_GPL(fb_find_logo);&&
&& & & &文件开始声明的一系列linux_logo结构体变量分别用来保存kernel/goldfish/drivers/video/logo目录下的一系列ppm或者pbm文件的内容的。这些ppm或者pbm文件都是用来描述第一个开机画面的。
&& & & &全局变量nologo是一个类型为布尔变量的模块参数,它的默认值等于0,表示要显示第一个开机画面。在这种情况下,函数fb_find_logo就会根据参数depth的值以及不同的编译选项来选择第一个开机画面的内容,并且保存在变量logo中返回给调用者。
&& & & &这一步执行完成之后,第一个开机画面的内容就保存在模块fbmem的全局变量fb_logo的成员变量logo中了。这时候控制台的初始化过程也结束了,接下来系统就会执行切换控制台的操作。前面提到,当系统执行切换控制台的操作的时候,模块fbcon中的函数fbcon_switch就会被调用。在调用的过程中,就会执行显示第一个开机画面的操作。
&& & & &函数fbcon_switch实现在文件kernel/goldfish/drivers/video/console/fbcon.c中,显示第一个开机画面的过程如下所示:
static&int&fbcon_switch(struct&vc_data&*vc)&&
&&&&&&&&struct&fb_info&*info,&*old_info&=&NULL;&&
&&&&&&&&struct&fbcon_ops&*&&
&&&&&&&&struct&display&*p&=&&fb_display[vc-&vc_num];&&
&&&&&&&&struct&fb_var_screeninfo&&&
&&&&&&&&int&i,&prev_console,&charcnt&=&256;&&
&&&&&&&&......&&
&&&&&&&&if&(logo_shown&==&FBCON_LOGO_DRAW)&{&&
&&&&&&&&&&&&&&&&logo_shown&=&fg_&&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&fb_show_logo(info,&ops-&rotate);&&
&&&&&&&&&&&&&&&&......&&
&&&&&&&&&&&&&&&&return&0;&&
&&&&&&&&}&&
&&&&&&&&return&1;&&
&& & & &由于前面在准备第一个开机画面的内容的时候,全局变量logo_show的值被设置为FBCON_LOGO_DRAW,因此,接下来就会调用函数fb_show_logo来显示第一个开机画面。在显示之前,这个函数会将全局变量logo_shown的值设置为fg_console,后者表示系统当前可见的控制台的编号。
&& & & &函数fb_show_logo实现在文件kernel/goldfish/drivers/video/fbmem.c中,如下所示:
int&fb_show_logo(struct&fb_info&*info,&int&rotate)&&
&&&&&&&&int&y;&&
&&&&&&&&y&=&fb_show_logo_line(info,&rotate,&fb_logo.logo,&0,&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&num_online_cpus());&&
&&&&&&&&......&&
&&&&&&&&return&y;&&
&& & & 这个函数调用另外一个函数fb_show_logo_line来进一步执行渲染第一个开机画面的操作。
&& & & 函数fb_show_logo_line也是实现在文件kernel/goldfish/drivers/video/fbmem.c中,如下所示:
static&int&fb_show_logo_line(struct&fb_info&*info,&int&rotate,&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&const&struct&linux_logo&*logo,&int&y,&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&unsigned&int&n)&&
&&&&&&&&u32&*palette&=&NULL,&*saved_pseudo_palette&=&NULL;&&
&&&&&&&&unsigned&char&*logo_new&=&NULL,&*logo_rotate&=&NULL;&&
&&&&&&&&struct&fb_image&&&
&&&&&&&&&&
&&&&&&&&if&(logo&==&NULL&||&info-&state&!=&FBINFO_STATE_RUNNING&||&&
&&&&&&&&&&&&info-&flags&&&FBINFO_MODULE)&&
&&&&&&&&&&&&&&&&return&0;&&
&&&&&&&&image.depth&=&8;&&
&&&&&&&&image.data&=&logo-&&&
&&&&&&&&if&(fb_logo.needs_cmapreset)&&
&&&&&&&&&&&&&&&&fb_set_logocmap(info,&logo);&&
&&&&&&&&if&(fb_logo.needs_truepalette&||&&
&&&&&&&&&&&&fb_logo.needs_directpalette)&{&&
&&&&&&&&&&&&&&&&palette&=&kmalloc(256&*&4,&GFP_KERNEL);&&
&&&&&&&&&&&&&&&&if&(palette&==&NULL)&&
&&&&&&&&&&&&&&&&&&&&&&&&return&0;&&
&&&&&&&&&&&&&&&&if&(fb_logo.needs_truepalette)&&
&&&&&&&&&&&&&&&&&&&&&&&&fb_set_logo_truepalette(info,&logo,&palette);&&
&&&&&&&&&&&&&&&&else&&
&&&&&&&&&&&&&&&&&&&&&&&&fb_set_logo_directpalette(info,&logo,&palette);&&
&&&&&&&&&&&&&&&&saved_pseudo_palette&=&info-&pseudo_&&
&&&&&&&&&&&&&&&&info-&pseudo_palette&=&&&
&&&&&&&&}&&
&&&&&&&&if&(fb_logo.depth&&=&4)&{&&
&&&&&&&&&&&&&&&&logo_new&=&kmalloc(logo-&width&*&logo-&height,&GFP_KERNEL);&&
&&&&&&&&&&&&&&&&if&(logo_new&==&NULL)&{&&
&&&&&&&&&&&&&&&&&&&&&&&&kfree(palette);&&
&&&&&&&&&&&&&&&&&&&&&&&&if&(saved_pseudo_palette)&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&info-&pseudo_palette&=&saved_pseudo_&&
&&&&&&&&&&&&&&&&&&&&&&&&return&0;&&
&&&&&&&&&&&&&&&&}&&
&&&&&&&&&&&&&&&&image.data&=&logo_&&
&&&&&&&&&&&&&&&&fb_set_logo(info,&logo,&logo_new,&fb_logo.depth);&&
&&&&&&&&}&&
&&&&&&&&image.dx&=&0;&&
&&&&&&&&image.dy&=&y;&&
&&&&&&&&image.width&=&logo-&&&
&&&&&&&&image.height&=&logo-&&&
&&&&&&&&if&(rotate)&{&&
&&&&&&&&&&&&&&&&logo_rotate&=&kmalloc(logo-&width&*&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&logo-&height,&GFP_KERNEL);&&
&&&&&&&&&&&&&&&&if&(logo_rotate)&&
&&&&&&&&&&&&&&&&&&&&&&&&fb_rotate_logo(info,&logo_rotate,&&image,&rotate);&&
&&&&&&&&}&&
&&&&&&&&fb_do_show_logo(info,&&image,&rotate,&n);&&
&&&&&&&&kfree(palette);&&
&&&&&&&&if&(saved_pseudo_palette&!=&NULL)&&
&&&&&&&&&&&&&&&&info-&pseudo_palette&=&saved_pseudo_&&
&&&&&&&&kfree(logo_new);&&
&&&&&&&&kfree(logo_rotate);&&
&&&&&&&&return&logo-&&&
&& & & &参数logo指向了前面所准备的第一个开机画面的内容。这个函数首先根据参数logo的内容来构造一个fb_image结构体image,用来描述最终要显示的第一个开机画面。最后就调用函数fb_do_show_logo来真正执行渲染第一个开机画面的操作。
&& & & &函数fb_do_show_logo也是实现在文件kernel/goldfish/drivers/video/fbmem.c中,如下所示:
static&void&fb_do_show_logo(struct&fb_info&*info,&struct&fb_image&*image,&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&int&rotate,&unsigned&int&num)&&
&&&&&&&&unsigned&int&x;&&
&&&&&&&&if&(rotate&==&FB_ROTATE_UR)&{&&
&&&&&&&&&&&&&&&&for&(x&=&0;&&
&&&&&&&&&&&&&&&&&&&&&x&&&num&&&&image-&dx&+&image-&width&&=&info-&var.&&
&&&&&&&&&&&&&&&&&&&&&x++)&{&&
&&&&&&&&&&&&&&&&&&&&&&&&info-&fbops-&fb_imageblit(info,&image);&&
&&&&&&&&&&&&&&&&&&&&&&&&image-&dx&+=&image-&width&+&8;&&
&&&&&&&&&&&&&&&&}&&
&&&&&&&&}&else&if&(rotate&==&FB_ROTATE_UD)&{&&
&&&&&&&&&&&&&&&&for&(x&=&0;&x&&&num&&&&image-&dx&&=&0;&x++)&{&&
&&&&&&&&&&&&&&&&&&&&&&&&info-&fbops-&fb_imageblit(info,&image);&&
&&&&&&&&&&&&&&&&&&&&&&&&image-&dx&-=&image-&width&+&8;&&
&&&&&&&&&&&&&&&&}&&
&&&&&&&&}&else&if&(rotate&==&FB_ROTATE_CW)&{&&
&&&&&&&&&&&&&&&&for&(x&=&0;&&
&&&&&&&&&&&&&&&&&&&&&x&&&num&&&&image-&dy&+&image-&height&&=&info-&var.&&
&&&&&&&&&&&&&&&&&&&&&x++)&{&&
&&&&&&&&&&&&&&&&&&&&&&&&info-&fbops-&fb_imageblit(info,&image);&&
&&&&&&&&&&&&&&&&&&&&&&&&image-&dy&+=&image-&height&+&8;&&
&&&&&&&&&&&&&&&&}&&
&&&&&&&&}&else&if&(rotate&==&FB_ROTATE_CCW)&{&&
&&&&&&&&&&&&&&&&for&(x&=&0;&x&&&num&&&&image-&dy&&=&0;&x++)&{&&
&&&&&&&&&&&&&&&&&&&&&&&&info-&fbops-&fb_imageblit(info,&image);&&
&&&&&&&&&&&&&&&&&&&&&&&&image-&dy&-=&image-&height&+&8;&&
&&&&&&&&&&&&&&&&}&&
&&&&&&&&}&&
&& & & 参数rotate用来描述屏幕的当前旋转方向。屏幕旋转方向不同,第一个开机画面的渲染方式也有所不同。例如,当屏幕上下颠倒时(FB_ROTATE_UD),第一个开机画面的左右顺序就刚好调换过来,这时候就需要从右到左来渲染。其它三个方向FB_ROTATE_UR、FB_ROTATE_CW和FB_ROTATE_CCW分别表示没有旋转、顺时针旋转90度和逆时针旋转90度。
&& & & 参数info用来描述要渲染的帧缓冲区硬件设备,它的成员变量fbops指向了一系列回调函数,用来操作帧缓冲区硬件设备,其中,回调函数fb_imageblit就是用来在指定的帧缓冲区硬件设备渲染指定的图像的。
&& & & 至此,第一个开机画面的显示过程就分析完成了。
&& & &2. 第二个开机画面的显示过程
&& & &由于第二个开机画面是在init进程启动的过程中显示的,因此,我们就从init进程的入口函数main开始分析第二个开机画面的显示过程。
&& & &init进程的入口函数main实现在文件system/core/init/init.c中,如下所示:
int&main(int&argc,&char&**argv)&&
&&&&int&fd_count&=&0;&&
&&&&struct&pollfd&ufds[4];&&
&&&&......&&
&&&&int&property_set_fd_init&=&0;&&
&&&&int&signal_fd_init&=&0;&&
&&&&int&keychord_fd_init&=&0;&&
&&&&if&(!strcmp(basename(argv[0]),&"ueventd"))&&
&&&&&&&&return&ueventd_main(argc,&argv);&&
&&&&......&&
&&&&queue_builtin_action(console_init_action,&"console_init");&&
&&&&......&&
&&&&for(;;)&{&&
&&&&&&&&int&nr,&i,&timeout&=&-1;&&
&&&&&&&&execute_one_command();&&
&&&&&&&&restart_processes();&&
&&&&&&&&if&(!property_set_fd_init&&&&get_property_set_fd()&&&0)&{&&
&&&&&&&&&&&&ufds[fd_count].fd&=&get_property_set_fd();&&
&&&&&&&&&&&&ufds[fd_count].events&=&POLLIN;&&
&&&&&&&&&&&&ufds[fd_count].revents&=&0;&&
&&&&&&&&&&&&fd_count++;&&
&&&&&&&&&&&&property_set_fd_init&=&1;&&
&&&&&&&&}&&
&&&&&&&&if&(!signal_fd_init&&&&get_signal_fd()&&&0)&{&&
&&&&&&&&&&&&ufds[fd_count].fd&=&get_signal_fd();&&
&&&&&&&&&&&&ufds[fd_count].events&=&POLLIN;&&
&&&&&&&&&&&&ufds[fd_count].revents&=&0;&&
&&&&&&&&&&&&fd_count++;&&
&&&&&&&&&&&&signal_fd_init&=&1;&&
&&&&&&&&}&&
&&&&&&&&if&(!keychord_fd_init&&&&get_keychord_fd()&&&0)&{&&
&&&&&&&&&&&&ufds[fd_count].fd&=&get_keychord_fd();&&
&&&&&&&&&&&&ufds[fd_count].events&=&POLLIN;&&
&&&&&&&&&&&&ufds[fd_count].revents&=&0;&&
&&&&&&&&&&&&fd_count++;&&
&&&&&&&&&&&&keychord_fd_init&=&1;&&
&&&&&&&&}&&
&&&&&&&&if&(process_needs_restart)&{&&
&&&&&&&&&&&&timeout&=&(process_needs_restart&-&gettime())&*&1000;&&
&&&&&&&&&&&&if&(timeout&&&0)&&
&&&&&&&&&&&&&&&&timeout&=&0;&&
&&&&&&&&}&&
&&&&&&&&if&(!action_queue_empty()&||&cur_action)&&
&&&&&&&&&&&&timeout&=&0;&&
&&&&&&&&......&&
&&&&&&&&nr&=&poll(ufds,&fd_count,&timeout);&&
&&&&&&&&if&(nr&&=&0)&&
&&&&&&&&&&&&continue;&&
&&&&&&&&for&(i&=&0;&i&&&fd_&i++)&{&&
&&&&&&&&&&&&if&(ufds[i].revents&==&POLLIN)&{&&
&&&&&&&&&&&&&&&&if&(ufds[i].fd&==&get_property_set_fd())&&
&&&&&&&&&&&&&&&&&&&&handle_property_set_fd();&&
&&&&&&&&&&&&&&&&else&if&(ufds[i].fd&==&get_keychord_fd())&&
&&&&&&&&&&&&&&&&&&&&handle_keychord();&&
&&&&&&&&&&&&&&&&else&if&(ufds[i].fd&==&get_signal_fd())&&
&&&&&&&&&&&&&&&&&&&&handle_signal();&&
&&&&&&&&&&&&}&&
&&&&&&&&}&&
&&&&return&0;&&
&& & & & 函数一开始就首先判断参数argv[0]的值是否等于&ueventd&,即当前正在启动的进程名称是否等于&ueventd&。如果是的话,那么就以ueventd_main函数来作入口函数。这是怎么回事呢?当前正在启动的进程不是init吗?它的名称怎么可能会等于&ueventd&?原来,在目标设备上,可执行文件/sbin/ueventd是可执行文件/init的一个符号链接文件,即应用程序ueventd和init运行的是同一个可执行文件。内核启动完成之后,可执行文件/init首先会被执行,即init进程会首先被启动。init进程在启动的过程中,会对启动脚本/init.rc进行解析。在启动脚本/init.rc中,配置了一个ueventd进程,它对应的可执行文件为/sbin/ueventd,即ueventd进程加载的可执行文件也为/init。因此,通过判断参数argv[0]的值,就可以知道当前正在启动的是init进程还是ueventd进程。
&& & & &ueventd进程是作什么用的呢?它是用来处理uevent事件的,即用来管理系统设备的。从前面的描述可以知道,它真正的入口函数为ueventd_main,实现在system/core/init/ueventd.c中。ueventd进程会通过一个socket接口来和内核通信,以便可以监控系统设备事件。例如,在前面一文中, 我们调用device_create函数来创建了一个名称为&hello&的字符设备,这时候内核就会向前面提到的socket发送一个设备增加事件。ueventd进程通过这个socket获得了这个设备增加事件之后,就会/dev目录下创建一个名称为&hello&的设备文件。这样用户空间的应用程序就可以通过设备文件/dev/hello来和驱动程序hello进行通信了。
&& & & &接下来调用另外一个函数queue_builtin_action来向init进程中的一个待执行action队列增加了一个名称等于&console_init&的action。这个action对应的执行函数为console_init_action,它就是用来显示第二个开机画面的。
&& & & &函数queue_builtin_action实现在文件system/core/init/init_parser.c文件中,如下所示:
static&list_declare(action_list);&&
static&list_declare(action_queue);&&
void&queue_builtin_action(int&(*func)(int&nargs,&char&**args),&char&*name)&&
&&&&struct&action&*&&
&&&&struct&command&*&&
&&&&act&=&calloc(1,&sizeof(*act));&&
&&&&act-&name&=&&&
&&&&list_init(&act-&commands);&&
&&&&cmd&=&calloc(1,&sizeof(*cmd));&&
&&&&cmd-&func&=&&&
&&&&cmd-&args[0]&=&&&
&&&&list_add_tail(&act-&commands,&&cmd-&clist);&&
&&&&list_add_tail(&action_list,&&act-&alist);&&
&&&&action_add_queue_tail(act);&&
void&action_add_queue_tail(struct&action&*act)&&
&&&&list_add_tail(&action_queue,&&act-&qlist);&&
&& & & action_list列表用来保存从启动脚本/init.rc解析得到的一系列action,以及一系列内建的action。当这些action需要执行的时候,它们就会被添加到action_queue列表中去,以便init进程可以执行它们。
&& & & 回到init进程的入口函数main中,最后init进程会进入到一个无限循环中去。在这个无限循环中,init进程会做以下五个事情:
&& & & A. 调用函数execute_one_command来检查action_queue列表是否为空。如果不为空的话,那么init进程就会将保存在列表头中的action移除,并且执行这个被移除的action。由于前面我们将一个名称为&console_init&的action添加到了action_queue列表中,因此,在这个无限循环中,这个action就会被执行,即函数console_init_action会被调用。
&& & & B. 调用函数restart_processes来检查系统中是否有进程需要重启。在启动脚本/init.rc中,我们可以指定一个进程在退出之后会自动重新启动。在这种情况下,函数restart_processes就会检查是否存在需要重新启动的进程,如果存在的话,那么就会将它重新启动起来。
&& & & C. 处理系统属性变化事件。当我们调用函数property_set来改变一个系统属性值时,系统就会通过一个socket(通过调用函数get_property_set_fd可以获得它的文件描述符)来向init进程发送一个属性值改变事件通知。init进程接收到这个属性值改变事件之后,就会调用函数handle_property_set_fd来进行相应的处理。后面在分析第三个开机画面的显示过程时,我们就会看到,SurfaceFlinger服务就是通过修改&ctl.start&和&ctl.stop&属性值来启动和停止第三个开机画面的。
&& & & D. 处理一种称为&chorded keyboard&的键盘输入事件。这种类型为chorded keyboard&的键盘设备通过不同的铵键组合来描述不同的命令或者操作,它对应的设备文件为/dev/keychord。我们可以通过调用函数get_keychord_fd来获得这个设备的文件描述符,以便可以监控它的输入事件,并且调用函数handle_keychord来对这些输入事件进行处理。
&& & & E. 回收僵尸进程。我们知道,在Linux内核中,如果父进程不等待子进程结束就退出,那么当子进程结束的时候,就会变成一个僵尸进程,从而占用系统的资源。为了回收这些僵尸进程,init进程会安装一个SIGCHLD信号接收器。当那些父进程已经退出了的子进程退出的时候,内核就会发出一个SIGCHLD信号给init进程。init进程可以通过一个socket(通过调用函数get_signal_fd可以获得它的文件描述符)来将接收到的SIGCHLD信号读取回来,并且调用函数handle_signal来对接收到的SIGCHLD信号进行处理,即回收那些已经变成了僵尸的子进程。
&& & &注意,由于后面三个事件都是可以通过文件描述符来描述的,因此,init进程的入口函数main使用poll机制来同时轮询它们,以便可以提高效率。
&& & &接下来我们就重点分析函数console_init_action的实现,以便可以了解第二个开机画面的显示过程:
static&int&console_init_action(int&nargs,&char&**args)&&
&&&&int&&&
&&&&char&tmp[PROP_VALUE_MAX];&&
&&&&if&(console[0])&{&&
&&&&&&&&snprintf(tmp,&sizeof(tmp),&"/dev/%s",&console);&&
&&&&&&&&console_name&=&strdup(tmp);&&
&&&&fd&=&open(console_name,&O_RDWR);&&
&&&&if&(fd&&=&0)&&
&&&&&&&&have_console&=&1;&&
&&&&close(fd);&&
&&&&if(&load_565rle_image(INIT_IMAGE_FILE)&)&{&&
&&&&&&&&fd&=&open("/dev/tty0",&O_WRONLY);&&
&&&&&&&&if&(fd&&=&0)&{&&
&&&&&&&&&&&&const&char&*&&
&&&&&&&&&&&&&&&&msg&=&"\n"&&
&&&&&&&&&&&&"\n"&&
&&&&&&&&&&&&"\n"&&
&&&&&&&&&&&&"\n"&&
&&&&&&&&&&&&"\n"&&
&&&&&&&&&&&&"\n"&&
&&&&&&&&&&&&"\n"&&&&
&&&&&&&&&&&&"\n"&&
&&&&&&&&&&&&"\n"&&
&&&&&&&&&&&&"\n"&&
&&&&&&&&&&&&"\n"&&
&&&&&&&&&&&&"\n"&&
&&&&&&&&&&&&"\n"&&
&&&&&&&&&&&&"\n"&&
&&&&&&&&&&&&"&&&&&&&&&&&&&A&N&D&R&O&I&D&";&&
&&&&&&&&&&&&write(fd,&msg,&strlen(msg));&&
&&&&&&&&&&&&close(fd);&&
&&&&&&&&}&&
&&&&return&0;&&
&& & & & 这个函数主要做了两件事件:
&& & & & A. 初始化控制台。init进程在启动的时候,会解析内核的启动参数(保存在文件/proc/cmdline中)。如果发现内核的启动参数中包含有了一个名称为&androidboot.console&的属性,那么就会将这个属性的值保存在字符数组console中。这样我们就可以通过设备文件/dev/&console&来访问系统的控制台。如果内核的启动参数没有包含名称为&androidboot.console&的属性,那么默认就通过设备文件/dev/console来访问系统的控制台。如果能够成功地打开设备文件/dev/&console&或者/dev/console,那么就说明系统支持访问控制台,因此,全局变量have_console的就会被设置为1。
&& & & & B. 显示第二个开机画面。显示第二个开机画面是通过调用函数load_565rle_image来实现的。在调用函数load_565rle_image的时候,指定的开机画面文件为INIT_IMAGE_FILE。INIT_IMAGE_FILE是一个宏,定义在system/core/init/init.h文件中,如下所示:
#define&INIT_IMAGE_FILE&"/initlogo.rle"&&
&& & & &即第二个开机画面的内容是由文件/initlogo.rle来指定的。如果文件/initlogo.rle不存在,或者在显示它的过程中出现异常,那么函数load_565rle_image的返回值就会等于-1,这时候函数console_init_action就以文本的方式来显示第二个开机画面,即向编号为0的控制台(/dev/tty0)输出&ANDROID&这7个字符。
&& & & &函数load_565rle_image实现在文件system/core/init/logo.c中,如下所示:
int&load_565rle_image(char&*fn)&&
&&&&struct&FB&&&
&&&&struct&stat&s;&&
&&&&unsigned&short&*data,&*bits,&*&&
&&&&unsigned&count,&&&
&&&&int&&&
&&&&if&(vt_set_mode(1))&&
&&&&&&&&return&-1;&&
&&&&fd&=&open(fn,&O_RDONLY);&&
&&&&if&(fd&&&0)&{&&
&&&&&&&&ERROR("cannot&open&'%s'\n",&fn);&&
&&&&&&&&goto&fail_restore_&&
&&&&if&(fstat(fd,&&s)&&&0)&{&&
&&&&&&&&goto&fail_close_&&
&&&&data&=&mmap(0,&s.st_size,&PROT_READ,&MAP_SHARED,&fd,&0);&&
&&&&if&(data&==&MAP_FAILED)&&
&&&&&&&&goto&fail_close_&&
&&&&if&(fb_open(&fb))&&
&&&&&&&&goto&fail_unmap_&&
&&&&max&=&fb_width(&fb)&*&fb_height(&fb);&&
&&&&ptr&=&&&
&&&&count&=&s.st_&&
&&&&bits&=&fb.&&
&&&&while&(count&&&3)&{&&
&&&&&&&&unsigned&n&=&ptr[0];&&
&&&&&&&&if&(n&&&max)&&
&&&&&&&&&&&&break;&&
&&&&&&&&android_memset16(bits,&ptr[1],&n&&&&1);&&
&&&&&&&&bits&+=&n;&&
&&&&&&&&max&-=&n;&&
&&&&&&&&ptr&+=&2;&&
&&&&&&&&count&-=&4;&&
&&&&munmap(data,&s.st_size);&&
&&&&fb_update(&fb);&&
&&&&fb_close(&fb);&&
&&&&close(fd);&&
&&&&unlink(fn);&&
&&&&return&0;&&
fail_unmap_data:&&
&&&&munmap(data,&s.st_size);&&
fail_close_file:&&
&&&&close(fd);&&
fail_restore_text:&&
&&&&vt_set_mode(0);&&
&&&&return&-1;&&
&& & & &函数首先将控制台的显示方式设置为图形方式,这是通过调用函数vt_set_mode来实现的,如下所示:
static&int&vt_set_mode(int&graphics)&&
&&&&int&fd,&r;&&
&&&&fd&=&open("/dev/tty0",&O_RDWR&|&O_SYNC);&&
&&&&if&(fd&&&0)&&
&&&&&&&&return&-1;&&
&&&&r&=&ioctl(fd,&KDSETMODE,&(void*)&(graphics&?&KD_GRAPHICS&:&KD_TEXT));&&
&&&&close(fd);&&
&&&&return&r;&&
&& & & &函数vt_set_mode首先打开控制台设备文件/dev/tty0,接着再通过IO控制命令KDSETMODE来将控制台的显示方式设置为文本方式或者图形方式,取决于参数graphics的值。从前面的调用过程可以知道,参数graphics的值等于1,因此,这里是将控制台的显示方式设备为图形方式。
&& & & &回到函数load_565rle_image中,从前面的调用过程可以知道,参数fn的值等于&/initlogo.rle&,即指向目标设备上的initlogo.rle文件。函数load_565rle_image首先调用函数open打开这个文件,并且将获得的文件描述符保存在变量fd中,接着再调用函数fstat来获得这个文件的大小。有了这些信息之后,函数load_565rle_image就可以调用函数mmap来把文件/initlogo.rle映射到init进程的地址空间来了,以便可以读取它的内容。
&& & & 将文件/initlogo.rle映射到init进程的地址空间之后,接下来再调用函数fb_open来打开设备文件/dev/graphics/fb0。前面在介绍第一个开机画面的显示过程中提到,设备文件/dev/graphics/fb0是用来访问系统的帧缓冲区硬件设备的,因此,打开了设备文件/dev/graphics/fb0之后,我们就可以将文件/initlogo.rle的内容输出到帧缓冲区硬件设备中去了。
&& & & 函数fb_open的实现如下所示:
static&int&fb_open(struct&FB&*fb)&&
&&&&fb-&fd&=&open("/dev/graphics/fb0",&O_RDWR);&&
&&&&if&(fb-&fd&&&0)&&
&&&&&&&&return&-1;&&
&&&&if&(ioctl(fb-&fd,&FBIOGET_FSCREENINFO,&&fb-&fi)&&&0)&&
&&&&&&&&goto&&&
&&&&if&(ioctl(fb-&fd,&FBIOGET_VSCREENINFO,&&fb-&vi)&&&0)&&
&&&&&&&&goto&&&
&&&&fb-&bits&=&mmap(0,&fb_size(fb),&PROT_READ&|&PROT_WRITE,&&
&&&&&&&&&&&&&&&&&&&&MAP_SHARED,&fb-&fd,&0);&&
&&&&if&(fb-&bits&==&MAP_FAILED)&&
&&&&&&&&goto&&&
&&&&return&0;&&
&&&&close(fb-&fd);&&
&&&&return&-1;&&
&& & & 打开了设备文件/dev/graphics/fb0之后,接着再分别通过IO控制命令FBIOGET_FSCREENINFO和FBIOGET_VSCREENINFO来获得帧缓冲硬件设备的固定信息和可变信息。固定信息使用一个fb_fix_screeninfo结构体来描述,它保存的是帧缓冲区硬件设备固有的特性,这些特性在帧缓冲区硬件设备被初始化了之后,就不会发生改变,例如屏幕大小以及物理地址等信息。可变信息使用一个fb_var_screeninfo结构体来描述,它保存的是帧缓冲区硬件设备可变的特性,这些特性在系统运行的期间是可以改变的,例如屏幕所使用的分辨率、颜色深度以及颜色格式等。
&& & & &除了获得帧缓冲区硬件设备的固定信息和可变信息之外,函数fb_open还会将设备文件/dev/graphics/fb0的内容映射到init进程的地址空间来,这样init进程就可以通过映射得到的虚拟地址来访问帧缓冲区硬件设备的内容了。
&& & & 回到函数load_565rle_image中,接下来分别使用宏fb_width和fb_height来获得屏幕所使用的的分辨率,即屏幕的宽度和高度。宏fb_width和fb_height的定义如下所示:
#define&fb_width(fb)&((fb)-&vi.xres)&&
#define&fb_height(fb)&((fb)-&vi.yres)&&
&& & & 屏幕的所使用的分辨率使用结构体fb_var_screeninfo的成员变量xres和yres来描述,其中,成员变量xres用来描述屏幕的宽度,而成员变量成员变量yres用来描述屏幕的高度。得到了屏幕的分辨率之后,就可以知道最多可以向帧缓冲区硬件设备写入的字节数的大小了,这个大小就等于屏幕的宽度乘以高度,保存在变量max中。
&& & & 现在我们分别得到了文件initlogo.rle和帧缓冲区硬件设备在init进程中的虚拟访问地址以及大小,这样我们就可以将文件initlogo.rle的内容写入到帧缓冲区硬件设备中去,以便可以将第二个开机画面显示出来,这是通过函数load_565rle_image中的while循环来实现的。
&& & & 文件initlogo.rle保存的第二个开机画面的图像格式是565rle的。rle的全称是run-length encoding,翻译为游程编码或者行程长度编码,它可以使用4个字节来描述一个连续的具有相同颜色值的序列。在rle565格式,前面2个字节中用来描述序列的个数,而后面2个字节用来描述一个具体的颜色,其中,颜色的RGB值分别占5位、6位和5位。理解了565rle图像格式之后,我们就可以理解函数load_565rle_image中的while循环的实现逻辑了。在每一次循环中,都会依次从文件initlogo.rle中读出4个字节,其中,前两个字节的内容保存在变量n中,而后面2个字节的内容用来写入到帧缓冲区硬件设备中去。由于2个字节刚好就可以使用一个无符号短整数来描述,因此,函数load_565rle_image通过调用函数android_memset16来将从文件initlogo.rle中读取出来的颜色值写入到帧缓冲区硬件设备中去,
&& & & 函数android_memset16的实现如下所示:
void&android_memset16(void&*_ptr,&unsigned&short&val,&unsigned&count)&&
&&&&unsigned&short&*ptr&=&_&&
&&&&count&&&=&1;&&
&&&&while(count--)&&
&&&&&&&&*ptr++&=&&&
&& & & 参数ptr指向被写入的地址,在我们这个场景中,这个地址即为帧缓冲区硬件设备映射到init进程中的虚拟地址值。
&& & & 参数val用来描述被写入的值,在我们这个场景中,这个值即为从文件initlogo.rle中读取出来的颜色值。
&& & & 参数count用来描述被写入的地址的长度,它是以字节为单位的。由于在将参数val的值写入到参数ptr所描述的地址中去时,是以无符号短整数为单位的,即是以2个字节为单位的,因此,函数android_memset16在将参数val写入到地址ptr中去之前,首先会将参数count的值除以2。相应的地,在函数load_565rle_image中,需要将具有相同颜色值的序列的个数乘以2之后,再调用函数android_memset16。
&& & & 回到函数load_565rle_image中,将文件/initlogo.rle的内容写入到帧缓冲区硬件设备去之后,第二个开机画面就可以显示出来了。接下来函数load_565rle_image就会调用函数munmap来注销文件/initlogo.rle在init进程中的映射,并且调用函数close来关闭文件/initlogo.rle。关闭了文件/initlogo.rle之后,还会调用函数unlink来删除目标设备上的/initlogo.rle文件。注意,这只是删除了目标设备上的/initlogo.rle文件,而不是删除ramdisk映像中的initlogo.rle文件,因此,每次关机启动之后,系统都会重新将ramdisk映像中的initlogo.rle文件安装到目标设备上的根目录来,这样就可以在每次开机的时候都能将它显示出来。
&& & & &除了需要注销文件/initlogo.rle在init进程中的映射和关闭文件/initlogo.rle之外,还需要注销文件/dev/graphics/fb0在init进程中的映射以及关闭文件/dev/graphics/fb0,这是通过调用fb_close函数来实现的,如下所示:
static&void&fb_close(struct&FB&*fb)&&
&&&&munmap(fb-&bits,&fb_size(fb));&&
&&&&close(fb-&fd);&&
&& & & 在调用fb_close函数之前,函数load_565rle_image还会调用另外一个函数fb_update来更新屏幕上的第二个开机画面,它的实现如下所示:
static&void&fb_update(struct&FB&*fb)&&
&&&&fb-&vi.yoffset&=&1;&&
&&&&ioctl(fb-&fd,&FBIOPUT_VSCREENINFO,&&fb-&vi);&&
&&&&fb-&vi.yoffset&=&0;&&
&&&&ioctl(fb-&fd,&FBIOPUT_VSCREENINFO,&&fb-&vi);&&
&& & & &在结构体fb_var_screeninfo中,除了使用成员变量xres和yres来描述屏幕所使用的分辨率之外,还使用成员变量xres_virtual和yres_virtual来描述屏幕所使用的虚拟分辨率。成员变量xres和yres所描述屏幕的分辨率称为可视分辨率。可视分辨率和虚拟分辨率有什么关系呢?可视分辨率是屏幕实际上使用的分辨率,即用户所看到的分辨率,而虚拟分辨率是在系统内部使用的,它是不可见的,并且可以大于可视分辨率。例如,假设可视分辨率是800 x 600,那么虚拟分辨率可以设置为1600 x 600。由于屏幕最多只可以显示800 x 600个像素,因此,在系统内部,就需要决定从1600 x 600中取出800 x 600个像素来显示,这是通过结构体fb_var_screeninfo的成员变量xoffset和yoffset的值来描述的。成员变量xoffset和yoffset的默认值等于0,即默认从虚拟分辨率的左上角取出与可视分辨率大小相等的像素出来显示,否则的话,就会根据成员变量xoffset和yoffset的值来从虚拟分辨率的中间位置取出与可视分辨率大小相等的像素出来显示。
&& & & &帧缓冲区的大小是由虚拟分辨率决定的,因此,我们就可以在帧缓冲中写入比屏幕大小还要多的像素值,多出来的这个部分像素值就可以用作双缓冲。我们仍然假设可视分辨率和虚拟分辨率分别是800 x 600和1600 x 600,那么我们就可以先将前一个图像的内容写入到帧缓冲区的前面800 x 600个像素中去,接着再将后一个图像的内容写入到帧缓冲区的后面800 x 600个像素中。通过分别将用来描述帧缓冲区硬件设备的fb_var_screeninfo结构体的成员变量yoffset的值设置为0和800,就可以平滑地显示两个图像。
&& & & &理解了帧缓冲区硬件设备的可视分辨性和虚拟分辨性之后,函数fb_update的实现逻辑就可以很好地理解了。&& & & &至此,第二个开机画面的显示过程就分析完成了。
&& & & &3. 第三个开机画面的显示过程
&& & & &第三个开机画面是由应用程序bootanimation来负责显示的。应用程序bootanimation在启动脚本init.rc中被配置成了一个服务,如下所示:
service&bootanim&/system/bin/bootanimation&&
&&&&user&graphics&&
&&&&group&graphics&&
&&&&disabled&&
&&&&oneshot&&
&& & & 应用程序bootanimation的用户和用户组名称分别被设置为graphics。注意,&用来启动应用程序bootanimation的服务是disable的,即init进程在启动的时候,不会主动将应用程序bootanimation启动起来。当SurfaceFlinger服务启动的时候,它会通过修改系统属性ctl.start的值来通知init进程启动应用程序bootanimation,以便可以显示第三个开机画面,而当System进程将系统中的关键服务都启动起来之后,ActivityManagerService服务就会通知SurfaceFlinger服务来修改系统属性ctl.stop的值,以便可以通知init进程停止执行应用程序bootanimation,即停止显示第三个开机画面。接下来我们就分别分析第三个开机画面的显示过程和停止过程。
&& & &从前面一文可以知道,Zygote进程在启动的过程中,会将System进程启动起来,而从前面又可以知道,System进程在启动的过程(Step 3)中,会调用SurfaceFlinger类的静态成员函数instantiate来启动SurfaceFlinger服务。Sytem进程在启动SurfaceFlinger服务的过程中,首先会创建一个SurfaceFlinger实例,然后再将这个实例注册到Service Manager中去。在注册的过程,前面创建的SurfaceFlinger实例会被一个sp指针引用。从前面可以知道,当一个对象第一次被智能指针引用的时候,这个对象的成员函数onFirstRef就会被调用。由于SurfaceFlinger重写了父类RefBase的成员函数onFirstRef,因此,在注册SurfaceFlinger服务的过程中,将会调用SurfaceFlinger类的成员函数onFirstRef。在调用的过程,就会创建一个线程来启动第三个开机画面。
&& & &&SurfaceFlinger类实现在文件frameworks/base/services/surfaceflinger/SurfaceFlinger.cpp 中,它的成员函数onFirstRef的实现如下所示:
void&SurfaceFlinger::onFirstRef()&&
&&&&run("SurfaceFlinger",&PRIORITY_URGENT_DISPLAY);&&
&&&&mReadyToRunBarrier.wait();&&
&& & & &SurfaceFlinger类继承了Thread类,当它的成员函数run被调用的时候,系统就会创建一个新的线程。这个线程在第一次运行之前,会调用SurfaceFlinger类的成员函数readyToRun来通知SurfaceFlinger,它准备就绪了。当这个线程准备就绪之后,它就会循环执行SurfaceFlinger类的成员函数threadLoop,直到这个成员函数的返回值等于false为止。
&& & & &注意,SurfaceFlinger类的成员函数onFirstRef是在System进程的主线程中调用的,它需要等待前面创建的线程准备就绪之后,再继续往前执行,这个通过调用SurfaceFlinger类的成员变量mReadytoRunBarrier所描述的一个Barrier对象的成员函数wait来实现的。每一个Barrier对象内问都封装了一个条件变量(Condition Variable),而条件变量是用来同步线程的。
&& & & &接下来,我们继续分析SurfaceFlinger类的成员函数readyToRun的实现,如下所示:
status_t&SurfaceFlinger::readyToRun()&&
&&&&LOGI(&&&"SurfaceFlinger's&main&thread&ready&to&run.&"&&
&&&&&&&&&&&&"Initializing&graphics&H/W...");&&
&&&&......&&
&&&&mReadyToRunBarrier.open();&&
&&&&property_set("ctl.start",&"bootanim");&&
&&&&return&NO_ERROR;&&
&& & & 前面创建的线程用作SurfaceFlinger的主线程。这个线程在启动的时候,会对设备主屏幕以及OpenGL库进行初始化。初始化完成之后,接着就会调用SurfaceFlinger类的成员变量mReadyToRunBarrier所描述的一个Barrier对象的成员函数open来唤醒System进程的主线程,以便它可以继续往前执行。最后,SurfaceFlinger类的成员函数readyToRun的成员函数会调用函数property_set来将系统属性&ctl.start&的值设置为&bootanim&,表示要将应用程序bootanimation启动起来,以便可以显示第三个开机画面。
&& & & 前面在介绍第二个开机画面的时候提到,当系统属性发生改变时,init进程就会接收到一个系统属性变化通知,这个通知最终是由在init进程中的函数handle_property_set_fd来处理的。
&& & & 函数handle_property_set_fd实现在文件system/core/init/property_service.c中,如下所示:
void&handle_property_set_fd()&&
&&&&prop_msg&&&
&&&&int&s;&&
&&&&int&r;&&
&&&&int&&&
&&&&struct&ucred&&&
&&&&struct&sockaddr_un&&&
&&&&socklen_t&addr_size&=&sizeof(addr);&&
&&&&socklen_t&cr_size&=&sizeof(cr);&&
&&&&if&((s&=&accept(property_set_fd,&(struct&sockaddr&*)&&addr,&&addr_size))&&&0)&{&&
&&&&&&&&return;&&
&&&&if&(getsockopt(s,&SOL_SOCKET,&SO_PEERCRED,&&cr,&&cr_size)&&&0)&{&&
&&&&&&&&close(s);&&
&&&&&&&&ERROR("Unable&to&recieve&socket&options\n");&&
&&&&&&&&return;&&
&&&&r&=&recv(s,&&msg,&sizeof(msg),&0);&&
&&&&close(s);&&
&&&&if(r&!=&sizeof(prop_msg))&{&&
&&&&&&&&ERROR("sys_prop:&mis-match&msg&size&recieved:&%d&expected:&%d\n",&&
&&&&&&&&&&&&&&r,&sizeof(prop_msg));&&
&&&&&&&&return;&&
&&&&switch(msg.cmd)&{&&
&&&&case&PROP_MSG_SETPROP:&&
&&&&&&&&msg.name[PROP_NAME_MAX-1]&=&0;&&
&&&&&&&&msg.value[PROP_VALUE_MAX-1]&=&0;&&
&&&&&&&&if(memcmp(msg.name,"ctl.",4)&==&0)&{&&
&&&&&&&&&&&&if&(check_control_perms(msg.value,&cr.uid,&cr.gid))&{&&
&&&&&&&&&&&&&&&&handle_control_message((char*)&msg.name&+&4,&(char*)&msg.value);&&
&&&&&&&&&&&&}&else&{&&
&&&&&&&&&&&&&&&&ERROR("sys_prop:&Unable&to&%s&service&ctl&[%s]&uid:&%d&pid:%d\n",&&
&&&&&&&&&&&&&&&&&&&&&&&&msg.name&+&4,&msg.value,&cr.uid,&cr.pid);&&
&&&&&&&&&&&&}&&
&&&&&&&&}&else&{&&
&&&&&&&&&&&&if&(check_perms(msg.name,&cr.uid,&cr.gid))&{&&
&&&&&&&&&&&&&&&&property_set((char*)&msg.name,&(char*)&msg.value);&&
&&&&&&&&&&&&}&else&{&&
&&&&&&&&&&&&&&&&ERROR("sys_prop:&permission&denied&uid:%d&&name:%s\n",&&
&&&&&&&&&&&&&&&&&&&&&&cr.uid,&msg.name);&&
&&&&&&&&&&&&}&&
&&&&&&&&}&&
&&&&&&&&break;&&
&&&&default:&&
&&&&&&&&break;&&
&& & & &init进程是通过一个socket来接收系统属性变化事件的。每一个系统属性变化事件的内容都是通过一个prop_msg对象来描述的。在prop_msg对象对,成员变量name用来描述发生变化的系统属性的名称,而成员变量value用来描述发生变化的系统属性的值。系统属性分为两种类型,一种是普通类型的系统属性,另一种是控制类型的系统属性(属性名称以&ctl.&开头)。控制类型的系统属性在发生变化时,会触发init进程执行一个命令,而普通类型的系统属性就不具有这个特性。注意,改变系统属性是需要权限,因此,函数handle_property_set_fd在处理一个系统属性变化事件之前,首先会检查修改系统属性的进程是否具有相应的权限,这是通过调用函数check_control_perms或者check_perms来实现的。
&& & & &从前面的调用过程可以知道,当前发生变化的系统属性的名称为&ctl.start&,它的值被设置为&bootanim&。由于这是一个控制类型的系统属性,因此,在通过了权限检查之后,另外一个函数handle_control_message就会被调用,以便可以执行一个名称为&bootanim&的命令。
&& & & &函数handle_control_message实现在system/core/init/init.c中,如下所示:
void&handle_control_message(const&char&*msg,&const&char&*arg)&&
&&&&if&(!strcmp(msg,"start"))&{&&
&&&&&&&&msg_start(arg);&&
&&&&}&else&if&(!strcmp(msg,"stop"))&{&&
&&&&&&&&msg_stop(arg);&&
&&&&}&else&{&&
&&&&&&&&ERROR("unknown&control&msg&'%s'\n",&msg);&&
&& & & 控制类型的系统属性的名称是以"ctl."开头,并且是以&start&或者&stop&结尾的,其中,&start&表示要启动某一个服务,而&stop&表示要停止某一个服务,它们是分别通过函数msg_start和msg_stop来实现的。由于当前发生变化的系统属性是以&start&来结尾的,因此,接下来就会调用函数msg_start来启动一个名称为&bootanim&的服务。&
&& & & 函数msg_start实现在文件system/core/init/init.c中,如下所示:
static&void&msg_start(const&char&*name)&&
&&&&struct&service&*&&
&&&&char&*tmp&=&NULL;&&
&&&&char&*args&=&NULL;&&
&&&&if&(!strchr(name,&':'))&&
&&&&&&&&svc&=&service_find_by_name(name);&&
&&&&else&{&&
&&&&&&&&tmp&=&strdup(name);&&
&&&&&&&&args&=&strchr(tmp,&':');&&
&&&&&&&&*args&=&'\0';&&
&&&&&&&&args++;&&
&&&&&&&&svc&=&service_find_by_name(tmp);&&
&&&&if&(svc)&{&&
&&&&&&&&service_start(svc,&args);&&
&&&&}&else&{&&
&&&&&&&&ERROR("no&such&service&'%s'\n",&name);&&
&&&&if&(tmp)&&
&&&&&&&&free(tmp);&&
&& & & 参数name的值等于&bootanim&,它用来描述一个服务名称。这个函数首先调用函数service_find_by_name来找到名称等于&bootanim&的服务的信息,这些信息保存在一个service结构体svc中,接着再调用另外一个函数service_start来将对应的应用程序启动起来。
&& & &从前面的内容可以知道,名称等于&bootanim&的服务所对应的应用程序为/system/bin/bootanimation,这个应用程序实现在frameworks/base/cmds/bootanimation目录中,其中,应用程序入口函数main是实现在frameworks/base/cmds/bootanimation/bootanimation_main.cpp中的,如下所示:
int&main(int&argc,&char**&argv)&&
#if&defined(HAVE_PTHREADS)&&
&&&&setpriority(PRIO_PROCESS,&0,&ANDROID_PRIORITY_DISPLAY);&&
&&&&char&value[PROPERTY_VALUE_MAX];&&
&&&&property_get("debug.sf.nobootanimation",&value,&"0");&&
&&&&int&noBootAnimation&=&atoi(value);&&
&&&&LOGI_IF(noBootAnimation,&&"boot&animation&disabled");&&
&&&&if&(!noBootAnimation)&{&&
&&&&&&&&sp&ProcessState&&proc(ProcessState::self());&&
&&&&&&&&ProcessState::self()-&startThreadPool();&&
&&&&&&&&&&
&&&&&&&&sp&BootAnimation&&boot&=&new&BootAnimation();&&
&&&&&&&&IPCThreadState::self()-&joinThreadPool();&&
&&&&return&0;&&
&& & & 这个函数首先检查系统属性&debug.sf.nobootnimaition&的值是否不等于0。如果不等于的话,那么接下来就会启动一个Binder线程池,并且创建一个BootAnimation对象。这个BootAnimation对象就是用来显示第三个开机画面的。由于BootAnimation对象在显示第三个开机画面的过程中,需要与SurfaceFlinger服务通信,因此,应用程序bootanimation就需要启动一个Binder线程池。
&& & &&BootAnimation类间接地继承了RefBase类,并且重写了RefBase类的成员函数onFirstRef,因此,当一个BootAnimation对象第一次被智能指针引用的时,这个BootAnimation对象的成员函数onFirstRef就会被调用。
&& & &&BootAnimation类的成员函数onFirstRef实现在文件frameworks/base/cmds/bootanimation/BootAnimation.cpp中,如下所示:
void&BootAnimation::onFirstRef()&{&&
&&&&status_t&err&=&mSession-&linkToComposerDeath(this);&&
&&&&LOGE_IF(err,&"linkToComposerDeath&failed&(%s)&",&strerror(-err));&&
&&&&if&(err&==&NO_ERROR)&{&&
&&&&&&&&run("BootAnimation",&PRIORITY_DISPLAY);&&
&& & & mSession是BootAnimation类的一个成员变量,它的类型为SurfaceComposerClient,是用来和SurfaceFlinger执行Binder进程间通信的,它是在BootAnimation类的构造函数中创建的,如下所示:
BootAnimation::BootAnimation()&:&Thread(false)&&
&&&&mSession&=&new&SurfaceComposerClient();&&
&& & & &&SurfaceComposerClient类内部有一个实现了ISurfaceComposerClient接口的Binder代理对象mClient,这个Binder代理对象引用了SurfaceFlinger服务,SurfaceComposerClient类就是通过它来和SurfaceFlinger服务通信的。
&& & & & 回到BootAnimation类的成员函数onFirstRef中,由于BootAnimation类引用了SurfaceFlinger服务,因此,当SurfaceFlinger服务意外死亡时,BootAnimation类就需要得到通知,这是通过调用成员变量mSession的成员函数linkToComposerDeath来注册SurfaceFlinger服务的死亡接收通知来实现的。
&& & & &BootAnimation类继承了Thread类,因此,当BootAnimation类的成员函数onFirstRef调用了父类Thread的成员函数run之后,系统就会创建一个线程,这个线程在第一次运行之前,会调用BootAnimation类的成员函数readyToRun来执行一些初始化工作,后面再调用BootAnimation类的成员函数htreadLoop来显示第三个开机画面。
&& & &&BootAnimation类的成员函数readyToRun的实现如下所示:
status_t&BootAnimation::readyToRun()&{&&
&&&&mAssets.addDefaultAssets();&&
&&&&DisplayInfo&&&
&&&&status_t&status&=&session()-&getDisplayInfo(0,&&dinfo);&&
&&&&if&(status)&&
&&&&&&&&return&-1;&&
&&&&sp&SurfaceControl&&control&=&session()-&createSurface(&&
&&&&&&&&&&&&getpid(),&0,&dinfo.w,&dinfo.h,&PIXEL_FORMAT_RGB_565);&&
&&&&session()-&openTransaction();&&
&&&&control-&setLayer(0x);&&
&&&&session()-&closeTransaction();&&
&&&&sp&Surface&&s&=&control-&getSurface();&&
&&&&const&EGLint&attribs[]&=&{&&
&&&&&&&&&&&&EGL_DEPTH_SIZE,&0,&&
&&&&&&&&&&&&EGL_NONE&&
&&&&EGLint&w,&h,&&&
&&&&EGLint&numC&&
&&&&EGLConfig&&&
&&&&EGLSurface&&&
&&&&EGLContext&&&
&&&&EGLDisplay&display&=&eglGetDisplay(EGL_DEFAULT_DISPLAY);&&
&&&&eglInitialize(display,&0,&0);&&
&&&&EGLUtils::selectConfigForNativeWindow(display,&attribs,&s.get(),&&config);&&
&&&&surface&=&eglCreateWindowSurface(display,&config,&s.get(),&NULL);&&
&&&&context&=&eglCreateContext(display,&config,&NULL,&NULL);&&
&&&&eglQuerySurface(display,&surface,&EGL_WIDTH,&&w);&&
&&&&eglQuerySurface(display,&surface,&EGL_HEIGHT,&&h);&&
&&&&if&(eglMakeCurrent(display,&surface,&surface,&context)&==&EGL_FALSE)&&
&&&&&&&&return&NO_INIT;&&
&&&&mDisplay&=&&&
&&&&mContext&=&&&
&&&&mSurface&=&&&
&&&&mWidth&=&w;&&
&&&&mHeight&=&h;&&
&&&&mFlingerSurfaceControl&=&&&
&&&&mFlingerSurface&=&s;&&
&&&&mAndroidAnimation&=&true;&&
&&&&if&((access(USER_BOOTANIMATION_FILE,&R_OK)&==&0)&&&&&
&&&&&&&&&&&&(mZip.open(USER_BOOTANIMATION_FILE)&==&NO_ERROR)&||&&
&&&&&&&&&&&&(access(SYSTEM_BOOTANIMATION_FILE,&R_OK)&==&0)&&&&&
&&&&&&&&&&&&(mZip.open(SYSTEM_BOOTANIMATION_FILE)&==&NO_ERROR))&&
&&&&&&&&mAndroidAnimation&=&false;&&
&&&&return&NO_ERROR;&&
&& & & &BootAnimation类的成员函数session用来返回BootAnimation类的成员变量mSession所描述的一个SurfaceComposerClient对象。通过调用SurfaceComposerClient对象mSession的成员函数createSurface可以获得一个SurfaceControl对象control。
&& & & &SurfaceComposerClient类的成员函数createSurface首先调用内部}

我要回帖

更多关于 康佳电视通电屏幕很暗 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信