Android系统的开机画面显示过程分析
分类:Android2012-07-0900:561252人阅读评论(39)收藏举报
好几个月都没有更新过博客了,从今天开始,老罗将尝试对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 内核编译选项可以参考在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)一文。
帧缓冲区硬件设备在内核中有一个对应的驱动程序模块fbmem,它实现在文件kernel/goldfish/drivers/video/fbmem.c中,它的初始化函数如下所示:
1/**
2*fbmem_init-init frame buffer subsystem
3*
4*Initialize the frame buffer subsystem.
5*
6*NOTE:This function is_only_to be called by drivers/char/mem.c.
7*
8*/
9
10static int__init
11fbmem_init(void)
12{
13proc_create("fb",0,NULL,&fb_proc_fops);
14
15if(register_chrdev(FB_MAJOR,"fb",&fb_fops))
16printk("unable to get major%d for fb devs\n",FB_MAJOR);
17
18fb_class=class_create(THIS_MODULE,"graphics");
19if(IS_ERR(fb_class)){
20printk(KERN_WARNING"Unable to create fb class;errno=%ld\n", PTR_ERR(fb_class));
21fb_class=NULL;
22}
23return0;
24}
这个函数首先调用函数proc_create在/proc目录下创建了一个fb文件,接着又调用函数register_chrdev来注册了一个名称为fb的字符设备,最后调用函数class_create在/sys/class目录下创建了一个graphics目录,用来描述内核的图形系统。
模块fbmem除了会执行上述初始化工作之外,还会导出一个函数
register_framebuffer:
25EXPORT_SYMBOL(register_framebuffer);
这个函数在内核的启动过程会被调用,以便用来执行注册帧缓冲区硬件设备的操作,它的实现如下所示:
26/**
27*register_framebuffer-registers a frame buffer device
28*@fb_info:frame buffer info structure
29*
30*Registers a frame buffer device@fb_info.
31*
32*Returns negative errno on error,or zero for success.
33*
34*/
35
36int
37register_framebuffer(struct fb_info*fb_info)
38{
39int i;
40struct fb_event event;
41......
42
43if(num_registered_fb==FB_MAX)
44return-ENXIO;
45
46......
47
48num_registered_fb++;
49for(i=0;i 50if(!registered_fb[i]) 51break; 52fb_info->node=i; 53mutex_init(&fb_info->lock); 54fb_info->dev=device_create(fb_class,fb_info->device, 55MKDEV(FB_MAJOR,i),NULL,"fb%d",i); 56if(IS_ERR(fb_info->dev)){ 57/*Not fatal*/ 58printk(KERN_WARNING"Unable to create device for framebuffer%d; errno=%ld\n",i,PTR_ERR(fb_info->dev)); 59fb_info->dev=NULL; 60}else 61fb_init_device(fb_info); 62 63...... 64 65registered_fb[i]=fb_info; 66 https://www.sodocs.net/doc/458886078.html,=fb_info; 68fb_notifier_call_chain(FB_EVENT_FB_REGISTERED,&event); 69return0; 70} 由于系统中可能会存在多个帧缓冲区硬件设备,因此,fbmem模块使用一个数组registered_fb保存所有已经注册了的帧缓冲区硬件设备,其中,每一个帧缓冲区硬件都是使用一个结构体fb_info来描述的。 我们知道,在Linux内核中,每一个硬件设备都有一个主设备号和一个从设备号,它们用来唯一地标识一个硬件设备。对于帧缓冲区硬件设备来说,它们的主设备号定义为FB_MAJOR(29),而从设备号则与注册的顺序有关,它们的值依次等于0,1,2等。 每一个被注册的帧缓冲区硬件设备在/dev目录和/sys/class/graphics目录下都有一个对应的设备文件fb 这个函数最后会通过调用函数fb_notifier_call_chain来通知帧缓冲区控制台,有一个新的帧缓冲区设备被注册到内核中来了。 帧缓冲区控制台在内核中对应的驱动程序模块为fbcon,它实现在文件 kernel/goldfish/drivers/video/console/fbconn.c中,它的初始化函数如下所示: 71static struct notifier_block fbcon_event_notifier={ 72.notifier_call=fbcon_event_notify, 73}; 74 75...... 76 77static int__init fb_console_init(void) 78{ 79int i; 80 81acquire_console_sem(); 82fb_register_client(&fbcon_event_notifier); 83fbcon_device=device_create(fb_class,NULL,MKDEV(0,0),NULL, 84"fbcon"); 85 86if(IS_ERR(fbcon_device)){ 87printk(KERN_WARNING"Unable to create device" 88"for fbcon;errno=%ld\n", 89PTR_ERR(fbcon_device)); 90fbcon_device=NULL; 91}else 92fbcon_init_device(); 93 94for(i=0;i 95con2fb_map[i]=-1; 96 97release_console_sem(); 98fbcon_start(); 99return0; 100} 这个函数除了会调用函数device_create来在/sys/class/graphics目录下创建一个fbconn文件之外,还会调用函数fb_register_client来监听帧缓冲区硬件设备的注册事件,这是由函数fbcon_event_notify来实现的,如下所示: 101static int fbcon_event_notify(struct notifier_block*self, 102unsigned long action,void*data) 103{ 104struct fb_event*event=data; 105struct fb_info*info=event->info; 106...... 107int ret=0; 108 109...... 110 111switch(action){ 112...... 113case FB_EVENT_FB_REGISTERED: 114ret=fbcon_fb_registered(info); 115break; 116...... 117 118} 119 120done: 121return ret; 122} 帧缓冲区硬件设备的注册事件最终是由函数fbcon_fb_registered来处理的,它的 实现如下所示: 123static int fbcon_fb_registered(struct fb_info*info) 124{ 125int ret=0,i,idx=info->node; 126 127fbcon_select_primary(info); 128 129if(info_idx==-1){ 130for(i=first_fb_vc;i<=last_fb_vc;i++){ 131if(con2fb_map_boot[i]==idx){ 132info_idx=idx; 133break; 134} 135} 136 137if(info_idx!=-1) 138ret=fbcon_takeover(1); 139}else{ 140for(i=first_fb_vc;i<=last_fb_vc;i++){ 141if(con2fb_map_boot[i]==idx) 142set_con2fb_map(i,idx,0); 143} 144} 145 146return ret; 147} 函数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的实现如下所示: 148static int fbcon_takeover(int show_logo) 149{ 150int err,i; 151 152if(!num_registered_fb) 153return-ENODEV; 154 155if(!show_logo) 156logo_shown=FBCON_LOGO_DONTSHOW; 157 158for(i=first_fb_vc;i<=last_fb_vc;i++) 159con2fb_map[i]=info_idx; 160 161err=take_over_console(&fb_con,first_fb_vc,last_fb_vc, 162fbcon_is_default); 163 164if(err){ 165for(i=first_fb_vc;i<=last_fb_vc;i++){ 166con2fb_map[i]=-1; 167} 168info_idx=-1; 169} 170 171return err; 172} 全局变量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来指定的,它的定义如下所示: 173/* 174*The console`switch'structure for the frame buffer based console 175*/ 176 177static const struct consw fb_con={ 178.owner=THIS_MODULE, 179.con_startup=fbcon_startup, 180.con_init=fbcon_init, 181.con_deinit=fbcon_deinit, 182.con_clear=fbcon_clear, 183.con_putc=fbcon_putc, 184.con_putcs=fbcon_putcs, 185.con_cursor=fbcon_cursor, 186.con_scroll=fbcon_scroll, 187.con_bmove=fbcon_bmove, 188.con_switch=fbcon_switch, 189.con_blank=fbcon_blank, 190.con_font_set=fbcon_set_font, 191.con_font_get=fbcon_get_font, 192.con_font_default=fbcon_set_def_font, 193.con_font_copy=fbcon_copy_font, 194.con_set_palette=fbcon_set_palette, 195.con_scrolldelta=fbcon_scrolldelta, 196.con_set_origin=fbcon_set_origin, 197.con_invert_region=fbcon_invert_region, 198.con_screen_pos=fbcon_screen_pos, 199.con_getxy=fbcon_getxy, 200.con_resize=fbcon_resize, 201}; 接下来我们主要关注函数fbcon_init和fbcon_switch的实现,系统就是通过它来初始化和切换控制台的。在初始化的过程中,会决定是否需要准备第一个开机画面的内容,而在切换控制台的过程中,会决定是否需要显示第一个开机画面的内容。 函数fbcon_init的实现如下所示: 202static void fbcon_init(struct vc_data*vc,int init) 203{ 204struct fb_info*info=registered_fb[con2fb_map[vc->vc_num]]; 205struct fbcon_ops*ops; 206struct vc_data**default_mode=vc->vc_display_fg; 207struct vc_data*svc=*default_mode; 208struct display*t,*p=&fb_display[vc->vc_num]; 209int logo=1,new_rows,new_cols,rows,cols,charcnt=256; 210int cap; 211 212if(info_idx==-1||info==NULL) 213return; 214 215...... 216 217if(vc!=svc||logo_shown==FBCON_LOGO_DONTSHOW|| 218(info->fix.type==FB_TYPE_TEXT)) 219logo=0; 220 221...... 222 223if(logo) 224fbcon_prepare_logo(vc,info,cols,rows,new_cols,new_rows); 225 226...... 227} 当前正在初始化的控制台使用参数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来准备的,如下所示: 228static void fbcon_prepare_logo(struct vc_data*vc,struct fb_info*info, 229int cols,int rows,int new_cols,int new_rows) 230{ 231...... 232 233int logo_height; 234 235...... 236 237logo_height=fb_prepare_logo(info,ops->rotate); 238 239...... 240 241if(logo_lines>vc->vc_bottom){ 242...... 243}else if(logo_shown!=FBCON_LOGO_DONTSHOW){ 244logo_shown=FBCON_LOGO_DRAW; 245...... 246} 247} 从函数fb_prepare_logo返回来之后,如果要显示的第一个开机画面所占用的控制台行数小于等于参数vc所描述的控制台的最大行数,并且全局变量logo_show的值不等于FBCON_LOGO_DRAW,那么就说明前面所提到的第一个开机画面可以显示在控制台中。这时候全局变量logo_show的值就会被设置为FBCON_LOGO_DRAW,表示第一个开机画面处于等待渲染的状态。 函数fb_prepare_logo实现在文件kernel/goldfish/drivers/video/fbmem.c中,如下所示: 248int fb_prepare_logo(struct fb_info*info,int rotate) 249{ 250int depth=fb_get_color_depth(&info->var,&info->fix); 251unsigned int yres; 252 253memset(&fb_logo,0,sizeof(struct logo_data)); 254 255...... 256 257if(info->fix.visual==FB_VISUAL_DIRECTCOLOR){ 258depth=info->var.blue.length; 259if(info->var.red.length 260depth=info->var.red.length; 261if(info->var.green.length 262depth=info->var.green.length; 263} 264 265if(info->fix.visual==FB_VISUAL_STATIC_PSEUDOCOLOR&&depth>4){ 266/*assume console colormap*/ 267depth=4; 268} 269 270/*Return if no suitable logo was found*/ 271fb_logo.logo=fb_find_logo(depth); 272 273...... 274 275return fb_prepare_extra_logos(info,fb_logo.logo->height,yres); 276} 这个函数首先得到参数info所描述的帧缓冲区硬件设备的颜色深度depth,接着再调用函数fb_find_logo来获得要显示的第一个开机画面的内容,并且保存在全局变量 fb_logo的成员变量logo中。 函数fb_find_logo实现在文件kernel/goldfish/drivers/video/logo/logo.c文件中,如下所示: 277extern const struct linux_logo logo_linux_mono; 278extern const struct linux_logo logo_linux_vga16; 279extern const struct linux_logo logo_linux_clut224; 280extern const struct linux_logo logo_blackfin_vga16; 281extern const struct linux_logo logo_blackfin_clut224; 282extern const struct linux_logo logo_dec_clut224; 283extern const struct linux_logo logo_mac_clut224; 284extern const struct linux_logo logo_parisc_clut224; 285extern const struct linux_logo logo_sgi_clut224; 286extern const struct linux_logo logo_sun_clut224; 287extern const struct linux_logo logo_superh_mono; 288extern const struct linux_logo logo_superh_vga16; 289extern const struct linux_logo logo_superh_clut224; 290extern const struct linux_logo logo_m32r_clut224; 291 292static int nologo; 293module_param(nologo,bool,0); 294MODULE_PARM_DESC(nologo,"Disables startup logo"); 295 296/*logo's are marked__https://www.sodocs.net/doc/458886078.html,e__init_refok to tell 297*modpost that it is intended that this function uses data 298*marked__initdata. 299*/ 300const struct linux_logo*__init_refok fb_find_logo(int depth) 301{ 302const struct linux_logo*logo=NULL; 303 304if(nologo) 305return NULL; 306 307if(depth>=1){ 308#ifdef CONFIG_LOGO_LINUX_MONO 309/*Generic Linux logo*/ 310logo=&logo_linux_mono; 311#endif 312#ifdef CONFIG_LOGO_SUPERH_MONO 313/*SuperH Linux logo*/ 314logo=&logo_superh_mono; 315#endif 316} 317 318if(depth>=4){ 319#ifdef CONFIG_LOGO_LINUX_VGA16 320/*Generic Linux logo*/ 321logo=&logo_linux_vga16; 322#endif 323#ifdef CONFIG_LOGO_BLACKFIN_VGA16 324/*Blackfin processor logo*/ 325logo=&logo_blackfin_vga16; 326#endif 327#ifdef CONFIG_LOGO_SUPERH_VGA16 328/*SuperH Linux logo*/ 329logo=&logo_superh_vga16; 330#endif 331} 332 333if(depth>=8){ 334#ifdef CONFIG_LOGO_LINUX_CLUT224 335/*Generic Linux logo*/ 336logo=&logo_linux_clut224; 337#endif 338#ifdef CONFIG_LOGO_BLACKFIN_CLUT224 339/*Blackfin Linux logo*/ 340logo=&logo_blackfin_clut224; 341#endif 342#ifdef CONFIG_LOGO_DEC_CLUT224 343/*DEC Linux logo on MIPS/MIPS64or ALPHA*/ 344logo=&logo_dec_clut224; 345#endif 346#ifdef CONFIG_LOGO_MAC_CLUT224 347/*Macintosh Linux logo on m68k*/ 348if(MACH_IS_MAC) 349logo=&logo_mac_clut224; 350#endif 351#ifdef CONFIG_LOGO_PARISC_CLUT224 352/*PA-RISC Linux logo*/ 353logo=&logo_parisc_clut224; 354#endif 355#ifdef CONFIG_LOGO_SGI_CLUT224 356/*SGI Linux logo on MIPS/MIPS64and VISWS*/ 357logo=&logo_sgi_clut224; 358#endif 359#ifdef CONFIG_LOGO_SUN_CLUT224 360/*Sun Linux logo*/ 361logo=&logo_sun_clut224; 362#endif 363#ifdef CONFIG_LOGO_SUPERH_CLUT224 364/*SuperH Linux logo*/ 365logo=&logo_superh_clut224; 366#endif 367#ifdef CONFIG_LOGO_M32R_CLUT224 368/*M32R Linux logo*/ 369logo=&logo_m32r_clut224; 370#endif 371} 372return logo; 373} 374EXPORT_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/fbconn.c中,显示第一个开机画面的过程如下所示: 375static int fbcon_switch(struct vc_data*vc) 376{ 377struct fb_info*info,*old_info=NULL; 378struct fbcon_ops*ops; 379struct display*p=&fb_display[vc->vc_num]; 380struct fb_var_screeninfo var; 381int i,prev_console,charcnt=256; 382 383...... 384 385if(logo_shown==FBCON_LOGO_DRAW){ 386logo_shown=fg_console; 387/*This is protected above by initmem_freed*/ 388fb_show_logo(info,ops->rotate); 389...... 390return0; 391} 392return1; 393} 由于前面在准备第一个开机画面的内容的时候,全局变量logo_show的值被设置为FBCON_LOGO_DRAW,因此,接下来就会调用函数fb_show_logo来显示第一个开机画面。在显示之前,这个函数会将全局变量logo_shown的值设置为fg_console,后者表示 系统当前可见的控制台的编号。 函数fb_show_logo实现在文件kernel/goldfish/drivers/video/fbmem.c中,如下所示: 394int fb_show_logo(struct fb_info*info,int rotate) 395{ 396int y; 397 398y=fb_show_logo_line(info,rotate,fb_logo.logo,0, 399num_online_cpus()); 400...... 401 402return y; 403} 这个函数调用另外一个函数fb_show_logo_line来进一步执行渲染第一个开机画面的操作。 函数fb_show_logo_line也是实现在文件kernel/goldfish/drivers/video/fbmem.c中,如下所示: 404static int fb_show_logo_line(struct fb_info*info,int rotate, 405const struct linux_logo*logo,int y, 406unsigned int n) 407{ 408u32*palette=NULL,*saved_pseudo_palette=NULL; 409unsigned char*logo_new=NULL,*logo_rotate=NULL; 410struct fb_image image; 411 412/*Return if the frame buffer is not mapped or suspended*/ 413if(logo==NULL||info->state!=FBINFO_STATE_RUNNING|| 414info->flags&FBINFO_MODULE) 415return0; 416 417image.depth=8; 418image.data=logo->data; 419 420if(fb_logo.needs_cmapreset) 421fb_set_logocmap(info,logo); 422 423if(fb_logo.needs_truepalette|| 424fb_logo.needs_directpalette){ 425palette=kmalloc(256*4,GFP_KERNEL); 426if(palette==NULL) 427return0; 428 429if(fb_logo.needs_truepalette) 430fb_set_logo_truepalette(info,logo,palette); 431else 432fb_set_logo_directpalette(info,logo,palette); 433 434saved_pseudo_palette=info->pseudo_palette; 435info->pseudo_palette=palette; 436} 437 438if(fb_logo.depth<=4){ 439logo_new=kmalloc(logo->width*logo->height,GFP_KERNEL); 440if(logo_new==NULL){ 441kfree(palette); 442if(saved_pseudo_palette) 443info->pseudo_palette=saved_pseudo_palette; 444return0; 445} 446image.data=logo_new; 447fb_set_logo(info,logo,logo_new,fb_logo.depth); 448} 449 450image.dx=0; 451image.dy=y; 452image.width=logo->width; 453image.height=logo->height; 454 455if(rotate){ 456logo_rotate=kmalloc(logo->width* 457logo->height,GFP_KERNEL); 458if(logo_rotate) 459fb_rotate_logo(info,logo_rotate,&image,rotate); 460} 461 462fb_do_show_logo(info,&image,rotate,n); 463 464kfree(palette); 465if(saved_pseudo_palette!=NULL) 466info->pseudo_palette=saved_pseudo_palette; 467kfree(logo_new); 468kfree(logo_rotate); 469return logo->height; 470} 参数logo指向了前面所准备的第一个开机画面的内容。这个函数首先根据参数logo的内容来构造一个fb_image结构体image,用来描述最终要显示的第一个开机画面。最后就调用函数fb_do_show_logo来真正执行渲染第一个开机画面的操作。 函数fb_do_show_logo也是实现在文件kernel/goldfish/drivers/video/fbmem.c中,如下所示: 471static void fb_do_show_logo(struct fb_info*info,struct fb_image*image, 472int rotate,unsigned int num) 473{ 474unsigned int x; 475 476if(rotate==FB_ROTATE_UR){ 477for(x=0; 478x 479x++){ 480info->fbops->fb_imageblit(info,image); 481image->dx+=image->width+8; 482} 483}else if(rotate==FB_ROTATE_UD){ 484for(x=0;x 485info->fbops->fb_imageblit(info,image); 486image->dx-=image->width+8; 487} 488}else if(rotate==FB_ROTATE_CW){ 489for(x=0; 490x 491x++){ 492info->fbops->fb_imageblit(info,image); 493image->dy+=image->height+8; 494} 495}else if(rotate==FB_ROTATE_CCW){ 496for(x=0;x 497info->fbops->fb_imageblit(info,image); 498image->dy-=image->height+8; 499} 500} 501} 参数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中,如下所示: 502int main(int argc,char**argv) 503{ 504int fd_count=0; 505struct pollfd ufds[4]; 506...... 507int property_set_fd_init=0; 508int signal_fd_init=0; 509int keychord_fd_init=0; 510 511if(!strcmp(basename(argv[0]),"ueventd")) 512return ueventd_main(argc,argv); 513 514...... 515 516queue_builtin_action(console_init_action,"console_init"); 517 518...... 519 520for(;;){ 521int nr,i,timeout=-1; 522 523execute_one_command(); 524restart_processes(); 525 526if(!property_set_fd_init&&get_property_set_fd()>0){ 527ufds[fd_count].fd=get_property_set_fd(); 528ufds[fd_count].events=POLLIN; 529ufds[fd_count].revents=0; 530fd_count++; 531property_set_fd_init=1; 532} 533if(!signal_fd_init&&get_signal_fd()>0){ 534ufds[fd_count].fd=get_signal_fd(); 535ufds[fd_count].events=POLLIN; 536ufds[fd_count].revents=0; 537fd_count++; 538signal_fd_init=1; 539} 540if(!keychord_fd_init&&get_keychord_fd()>0){ 541ufds[fd_count].fd=get_keychord_fd(); 542ufds[fd_count].events=POLLIN; 543ufds[fd_count].revents=0; 544fd_count++; 545keychord_fd_init=1; 546} 547 548if(process_needs_restart){ 549timeout=(process_needs_restart-gettime())*1000; 550if(timeout<0) 551timeout=0; 552} 553 554if(!action_queue_empty()||cur_action) 555timeout=0; 556 557...... 一,系统引导bootloader 加电,cpu执行bootloader程序,正常启动系统,加载boot.img【其中包含内核。还有ramdisk】 二,内核kernel bootloader加载kernel,kernel自解压,初始化,载入built-in驱动程序,完成启动。 内核启动后会创建若干内核线程,在后装入并执行程序/sbin/init/,载入init process,切换至用户空间(user-space) 内核zImage解压缩 head.S【这是ARM-Linux运行的第一个文件,这些代码是一个比较独立的代码包裹器。其作用就是解压Linux内核,并将PC指针跳到内核(vmlinux)的第一条指令】首先初始化自解压相关环境(内存等),调用decompress_kernel进行解压,解压后调用start_kernel启动内核【start_kernel是任何版本linux内核的通用初始化函数,它会初始化很多东西,输出linux版本信息,设置体系结构相关的环境,页表结构初始化,设置系 统自陷入口,初始化系统IRQ,初始化核心调度器等等】,最后调用rest_init【rest_init 会调用kernel_init启动init进程(缺省是/init)。然后执行schedule开始任务调度。这个init是由android的./system/core/init下的代码编译出来的,由此进入了android的代码】。 三,Init进程启动 【init是kernel启动的第一个进程,init启动以后,整个android系统就起来了】 init进程启动后,根据init.rc 和init. Android的开机流程 1. 系统引导bootloader 1) 源码:bootable/bootloader/* 2) 说明:加电后,CPU将先执行bootloader程序,此处有三种选择 a) 开机按Camera+Power启动到fastboot,即命令或SD卡烧写模式,不加载内核及文件系统,此处可以进行工厂模式的烧写 b) 开机按Home+Power启动到recovery模式,加载recovery.img,recovery.i mg包含内核,基本的文件系统,用于工程模式的烧写 c) 开机按Power,正常启动系统,加载boot.img,boot.img包含内核,基本文件系统,用于正常启动手机(以下只分析正常启动的情况) 2. 内核kernel 1) 源码:kernel/* 2) 说明:kernel由bootloader加载 3. 文件系统及应用init 1) 源码:system/core/init/* 2) 配置文件:system/rootdir/init.rc, 3) 说明:init是一个由内核启动的用户级进程,它按照init.rc中的设置执行:启动服务(这里的服务指linux底层服务,如adbd提供adb支持,vold提供SD卡挂载等),执行命令和按其中的配置语句执行相应功能 4. 重要的后台程序zygote 1)源码:frameworks/base/cmds/app_main.cpp等 2) 说明:zygote是一个在init.rc中被指定启动的服务,该服务对应的命令是/system/bin/app_process a)建立Java Runtime,建立虚拟机 b) 建立Socket接收ActivityManangerService的请求,用于Fork应用程序 c) 启动System Server 5. 系统服务system server 1)源码:frameworks/base/services/java/com/android/server/SystemServer.jav a 2) 说明:被zygote启动,通过SystemManager管理android的服务(这里的服务指frameworks/base/services下的服务,如卫星定位服务,剪切板服务等) 6. 桌面launcher 1)源码:ActivityManagerService.java为入口,packages/apps/launcher*实现 2) 说明:系统启动成功后SystemServer使用xxx.systemReady()通知各个服务,系统已经就绪,桌面程序Home就是在ActivityManagerService.systemReady()通知的过程中建立的,最终调用()启launcher 7. 解锁 1) 源码: frameworks/policies/base/phone/com/android/internal/policy/impl/*lock* 2) 说明:系统启动成功后SystemServer调用wm.systemReady()通知WindowManagerService,进而调用PhoneWindowManager,最终通过LockPatternKeyguardView显示解锁界面,跟踪代码可以看到解锁界面并不是一个Activity,这是只是向特定层上绘图,其代码了存放在特殊的位置 开机启动花了40多秒,正常开机只需要28秒就能开机起来。 内核的启动我没有去分析,另一个同事分析的。我主要是分析从SystemServer启来到开机动画结束显示解锁界面的这段时间,也就是开机动画的第三个动画开始到结束这段时间,这是个比较耗时阶段,一般都在17秒左右(见过牛B的手机,只需5秒)。 SystemServer分两步执行:init1和init2。init1主要是初始化native的服务,代码在sy stem_init.cpp的system_init,初始化了SurfaceFlinger和SensorService这两个native的服务。init2启动的是java的服务,比如ActivityManagerService、WindowManagerService、PackageManagerService等,在这个过程中PackageManagerService用的时间最长,因为PackageManagerService会去扫描特定目录下的jar包和apk文件。 在开机时间需要40多秒的时,从Log上可以看到,从SurfaceFlinger初始化到动画结束,要27秒左右的时间,即从SurfaceFlinger::init的LOGI("SurfaceFlinger is starting")这句Log到void SurfaceFlinger::bootFinished()的LOGI("Boot is finished (%ld ms)", long(ns 2ms(duration)) ),需要27秒左右的时间,这显然是太长了,但到底是慢在哪了呢?应该在个中间的点,二分一下,于是想到了以启动服务前后作为分隔:是服务启动慢了,还是在服务启动后的这段时间慢?以ActivityManagerService的Slog.i(TAG, "System now ready")的这句Log为分割点,对比了一下,在从SurfaceFlinger is starting到System now read y多了7秒左右的时间,这说明SystemServer在init1和init2过程中启动慢了,通过排查,发现在init1启动的时候,花了7秒多的时间,也就是system_init的LOGI("Entered system _init()")到LOGI("System server: starting Android runtime.\n")这段时间用了7秒多,而正常情况是400毫秒便可以初始化完,通过添加Log看到,在SensorService启动时,用了比较长的时间。 不断的添加Log发现,在启动SensorService时候,关闭设备文件变慢了,每次关闭一个/dev/input/下的设备文件需要100ms左右,而SensorService有60~70次的关闭文件,大概有7s左右的时间。 调用流程是: frameworks/base/cmds/system_server/library/system_init.cpp: system_init->SensorServi ce::instantiate frameworks/native/services/sensorservice/SensorService.cpp: void SensorService::onFi rstRef()->SensorDevice& dev(SensorDevice::getInstance()) hardware/libsensors/SensorDevice.cpp: SensorDevice::SensorDevice()->sensors_open hardware/libsensors/sensors.cpp: open_sensors->sensors_poll_context_t sensors_poll_context_t执行打开每个传感器设备时,遍历/dev/input/目录下的设备文件,以匹配当前需要打开的设备,遍历文件是在 hardware/libsensors/SensorBase.cpp的openInput下实现,如果打开的设备文件不是正在打开的设备文件,会执行下面语句的else部分: if (!strcmp(name, inputName)) { strcpy(input_name, filename); break; 基于MT6752的Android系统启动流程分析报告 1、Bootloader引导 (2) 2、Linux内核启动 (23) 3、Android系统启动 (23) 报告人: 日期:2016.09.03 对于Android整个启动过程来说,基本可以划分成三个阶段:Bootloader引导、Linux kernel启动、Android启动。但根据芯片架构和平台的不同,在启动的Bootloader阶段会有所差异。 本文以MTK的MT6752平台为例,分析一下基于该平台的Android系统启动流程。 1、Bootloader引导 1.1、Bootloader基本介绍 BootLoader是在操作系统运行之前运行的一段程序,它可以将系统的软硬件环境带到一个合适状态,为运行操作系统做好准备,目的就是引导linux操作系统及Android框架(framework)。 它的主要功能包括设置处理器和内存的频率、调试信息端口、可引导的存储设备等等。在可执行环境创建好之后,接下来把software装载到内存并执行。除了装载software,一个外部工具也能和bootloader握手(handshake),可指示设备进入不同的操作模式,比如USB下载模式和META模式。就算没有外部工具的握手,通过外部任何组合或是客户自定义按键,bootloader也能够进入这些模式。 由于不同处理器芯片厂商对arm core的封装差异比较大,所以不同的arm处理器,对于上电引导都是由特定处理器芯片厂商自己开发的程序,这个上电引导程序通常比较简单,会初始化硬件,提供下载模式等,然后才会加载通常的bootloader。 下面是几个arm平台的bootloader方案: marvell(pxa935) : bootROM + OBM + BLOB informax(im9815) : bootROM + barbox + U-boot mediatek(mt6517) : bootROM + pre-loader + U-boot broadcom(bcm2157) : bootROM + boot1/boot2 + U-boot 而对MT6752平台,MTK对bootloader引导方案又进行了调整,它将bootloader分为以下两个部分: (1) 第1部分bootloader,是MTK内部(in-house)的pre-loader,这部分依赖平台。 (2) 第2部分bootloader,是LK(little kernel的缩写,作用同常见的u-boot差不多),这部分依赖操作系统,负责引导linux操作系统和Android框架。 1.2、bootloader的工作流程 1.2.1 bootloader正常的启动流程 先来看启动流程图: linux内核启动+Android系统启动过程详解 第一部分:汇编部分 Linux启动之 linux-rk3288-tchip/kernel/arch/arm/boot/compressed/ head.S分析这段代码是linux boot后执行的第一个程序,完成的主要工作是解压内核,然后跳转到相关执行地址。这部分代码在做驱动开发时不需要改动,但分析其执行流程对是理解android的第一步 开头有一段宏定义这是gnu arm汇编的宏定义。关于GUN 的汇编和其他编译器,在指令语法上有很大差别,具体可查询相关GUN汇编语法了解 另外此段代码必须不能包括重定位部分。因为这时一开始必须要立即运行的。所谓重定位,比如当编译时某个文件用到外部符号是用动态链接库的方式,那么该文件生成的目标文件将包含重定位信息,在加载时需要重定位该符号,否则执行时将因找不到地址而出错 #ifdef DEBUG//开始是调试用,主要是一些打印输出函数,不用关心 #if defined(CONFIG_DEBUG_ICEDCC) ……具体代码略 #endif 宏定义结束之后定义了一个段, .section ".start", #alloc, #execinstr 这个段的段名是 .start,#alloc表示Section contains allocated data, #execinstr表示Section contains executable instructions. 生成最终映像时,这段代码会放在最开头 .align start: .type start,#function /*.type指定start这个符号是函数类型*/ .rept 8 mov r0, r0 //将此命令重复8次,相当于nop,这里是为中断向量保存空间 .endr b 1f .word 0x016f2818 @ Magic numbers to help the loader Android的开机流程 1. 系统引导bootloader 1) 源码: bootable/bootloader/* 2) 说明: 加电后, CPU将先执行bootloader程序, 此处有三种选择 a) 开机按Camera+Power启动到fastboot, 即命令或SD卡烧写模式, 不加载内核及文件系统, 此处能够进行工厂模式的烧写 b) 开机按Home+Power启动到recovery模式, 加载recovery.img, recovery.img包含内核, 基本的文件系统, 用于工程模式的烧写 c) 开机按Power, 正常启动系统, 加载boot.img, boot.img包含内核, 基本文件系统, 用于正常启动手机( 以下只分析正常启动的情况) 2. 内核kernel 1) 源码: kernel/* 2) 说明: kernel由bootloader加载 3. 文件系统及应用init 1) 源码: system/core/init/* 2) 配置文件: system/rootdir/init.rc, 3) 说明: init是一个由内核启动的用户级进程, 它按照init.rc中的设置执行: 启动服务( 这里的服务指linux底层服务, 如adbd提供adb支持, vold提供SD卡挂载等) , 执行命令和按其中的配置语句执行相应功能 4. 重要的后台程序zygote 1) 源码: frameworks/base/cmds/app_main.cpp等 2) 说明: zygote是一个在init.rc中被指定启动的服务, 该服务对应的命令是/system/bin/app_process Android系统启动过程详解 Android系统启动过程 首先Android框架架构图:(来自网上,我觉得这张图看起来很清晰) Linux内核启动之后就到Android Init进程,进而启动Android相关的服务和应用。 启动的过程如下图所示:(图片来自网上,后面有地址) 下面将从Android4.0源码中,和网络达人对此的总结中,对此过程加以学习了解和总结, 以下学习过程中代码片段中均有省略不完整,请参照源码。 一Init进程的启动 init进程,它是一个由内核启动的用户级进程。内核自行启动(已经被载入内存,开始运行, 并已初始化所有的设备驱动程序和数据结构等)之后,就通过启动一个用户级程序init的方式,完成引导进程。init始终是第一个进程。 启动过程就是代码init.c中main函数执行过程:system\core\init\init. c 在函数中执行了:文件夹建立,挂载,rc文件解析,属性设置,启动服务,执行动作,socket监听…… 下面看两个重要的过程:rc文件解析和服务启动。 1 rc文件解析 .rc文件是Android使用的初始化脚本文件(System/Core/Init/readm e.txt中有描述: four broad classes of statements which are Actions, Commands, Services, and Options.) 其中Command 就是系统支持的一系列命令,如:export,hostname,mkdir,mount,等等,其中一部分是linux 命令, 还有一些是android 添加的,如:class_start Android SystemBar启动流程分析 SystemBars的服务被start时,最终会调用该类的onNoService()方法。 @Override public void start() { if (DEBUG) Log.d(TAG, "start"); ServiceMonitor mServiceMonitor = new ServiceMonitor(TAG, DEBUG, mContext, Settings.Secure.BAR_SERVICE_COMPONENT, this); mServiceMonitor.start(); // will call onNoService if no remote service is found } @Override public void onNoService() { if (DEBUG) Log.d(TAG, "onNoService"); createStatusBarFromConfig(); // fallback to using an in-process implementation } private void createStatusBarFromConfig() { … mStatusBar = (BaseStatusBar) cls.newInstance(); … mStatusBar.start(); } BaseStatusBar是一个抽象类,故调用其子类的PhoneStatusBar的start 函数。 @Override public void start() { … super.start(); … } 子类的start又调用了父类的start public void start() { … createAndAddWindows(); … } Android L系统启动及加载流程分析 1、概述 Android L的启动可以分为几个步骤:Linux内核启动、init进程启动、native系统服务及java系统服务启动、Home启动,主要过程如下图: 图1 整个启动流程跟4.4及之前的版本相差不多,只是有个别不同之处,本文我们主要分析Linux内核启动之后的过程。 2、启动过程分析 2.1 init进程启动 当系统内核加载完成之后,会启动init守护进程,它是内核启动的第一个用户级进程,是Android的一个进程,进程号为1,init进程启动后执行入口函数main(),主要操作为: 图2 AndroidL上将selinux的安全等级提高了,设为了enforcing模式,4.4上是permissive模式。 解析rc脚本文件,即init.rc脚本,该文件是Android初始化脚本,定义了一系列的动作和执行这些动作的时间阶段e aryl-init、init、early-boot、boot、post-fs等阶段。init进程main 函数中会根据这些阶段进行解析执行。AndroidL上为了流程更清晰,增加了charger(充电开机)、ffbm(工厂模式)、以及late-init阶段,实际上这些阶段是对其他阶段的组合执行,比如late-init: 2.2 ServiceManager的启动 servicemanager的启动就是init进程通过init.rc脚本启动的: 源码在frameworks/native/cmds/servicemanager/service_manager.c中,servicemanager是服务管理器,它本身也是一个服务(handle=0),通过binder调用,为native和Java系统服务提供注册和查询服务的,即某个服务启动后,需要将自己注册到servicemanager中,供其他服务或者应用查询使用。AndroidL上servicemanger中在处理注册和查询动作之前添加了selinux安全检测相关的处理。 2.3 SurfaceFinger、MediaServer进程启动 Android4.4以前,surfacefinger的启动根据属性system_init.startsurfaceflinger,决定是通过init.rc启动还是systemserver进程启动,之后的版本包括AndoridL都是通过init.rc启动的: 启动后会向servicemanager进程注册服务中,该服务启动时主要功能是初始化整个显 android开机启动流程简单分析 android启动 当引导程序启动Linux内核后,会加载各种驱动和数据结构,当有了驱动以后,开始启动Android系统同时会加载用户级别的第一个进程init(system\core\init\init.cpp)代码如下: int main(int argc, char** argv) { ..... //创建文件夹,挂载 // Get the basic filesystem setup we need put together in the initramdisk // on / and then we'll let the rc file figure out the rest. if (is_first_stage) { mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"); mkdir("/dev/pts", 0755); mkdir("/dev/socket", 0755); mount("devpts", "/dev/pts", "devpts", 0, NULL); #define MAKE_STR(x) __STRING(x) mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)); mount("sysfs", "/sys", "sysfs", 0, NULL); } ..... //打印日志,设置log的级别 klog_init(); klog_set_level(KLOG_NOTICE_LEVEL); ..... Parser& parser = Parser::GetInstance(); parser.AddSectionParser("service",std::make_unique Android ninja编译启动过程分析 ---make是如何转换到到ninja编译的 1.首先你的得对make的工作机制有个大概的了解: 运行的命令在要编译的目录下运行make,或者make target_name a.分析处理保存阶段(没有实际编译动作):它首先对当前目录下的Makefile文件的做一次扫描,语法分析,还有处理,主要是变量的保存,目标依赖列表生成,目标下的action列表的生成,然后记住 b.然后按记住的目标执行action列表动作(有实际编译动作). 编译启动的入口方式还是运行make: 2开始make-jxxx方式进入.....(xxx是本机cpu的数量) make开始做进行第一次扫描.... 目前USE_NINJA还是没有定义,估计以后很久很久才能启用的了! BUILDING_WITH_NINJA开始也是没定义的 看make扫描入口文件: Makefile: include build/core/main.mk 在build/core/main.mk: 在ninia之前都有include help.mk和config.mk 97include$(BUILD_SYSTEM)/help.mk 98 99#Set up various standard variables based on configuration 100#and host information. 101include$(BUILD_SYSTEM)/config.mk 说明make help//显示make帮助make config//当前显示配置 103relaunch_with_ninja:= 104ifneq($(USE_NINJA),false) 105ifndef BUILDING_WITH_NINJA<==第二次扫描不会到这里了 106relaunch_with_ninja:=true 107endif 108endif 116ifeq($(relaunch_with_ninja),true)<===第一次扫描入这里了 117#Mark this is a ninja build. 118$(shell mkdir-p$(OUT_DIR)&&touch$(OUT_DIR)/ninja_build) 119include build/core/ninja.mk//---进入ninja.mk 第一次扫描到此为止就结束掉了,因为在当前ifeq else endif后面没有代码了 120else#///!relaunch_with_ninja<===第二次扫描入这里了 一、Android开机启动流程简介 1、OS-level: 由bootloader载入linux kernel后kernel开始初始化, 并载入built-in 的驱动程序。Kernel完成开机后,载入init process,切换至user-space。 Init进程是第一个在user-space启动的进程。 2、Android-level: 由init process读取init.rc,Native 服务启动,并启动重要的外部程序,例如:servicemanager、Zygote以及System Server等。 由 init process 根据硬件类型读取init.xxx.rc。由init.xxx.rc加载init.xxx.sh。 由 init.xxx.sh 加载特定的硬件驱动。如hi_tuner.ko、hi_demux.ko等。 3、Zygote-Mode: Zygote 启动完SystemServer 后,进入Zygote Mode,在Socket 等候命令。 随后,使用者将看到一个桌面环境(Home Screen)。桌面环境由一个名为[Launcher]的应用程序负责提供。 本文档重点研究Android-level中的启动流程。 启动流程如下图所示: 二、init process流程分析 init进程简介 init进程是第一个在user-space启动的进程。由内核启动参数[init]传递给内核,如果该项没有设置,内核会按 /etc/init,/bin/init,/sbin/init,/bin/sh的顺序进行尝试,如果都有的都没找到,内核会抛出 kernel panic:的错误。 A n d r o i d系统启动升级 流程 TTA standardization office【TTA 5AB- TTAK 08- TTA 2C】 摘要 本文首先介绍了Android系统更新要用到的一些概念:硬件、三种模式及相互之间的通信。然后介绍了Android系统的启动和升级流程。 概述 通常,Android系统的升级包名称为update.zip。Android系统内部自带了烧写升级包的工具,我们可以手动烧写,也可以通过某些机制自动更新系统。同时,我们可以手动修改和制作升级包。本文主要阐述在Android系统升级中用到的一些概念,本文只是作为索引,并不涉及到具体的烧写工作。本文基于Android系统的版本:4.0.4。 硬件 Android系统的烧写,是非常贴近硬件的。一是,烧写是在实实在在的硬件上操作的。二则,有时在翻阅源码的时候,需要知道硬件的类型,以便找到和硬件相对应的源码。 烧写相关的硬件主要有三部分:CPU、内存和nand flash。当然,只是相对本文而言。CPU用来执行程序中的指令。内存只是在运行中,将需要运行的程序加载其中并运行,关机后即消失。nandflash用来存储程序的数据,它会一直存在。系统启动时,会将nand flash上的操作系统加载到内存,然后运行在CPU 中,对于非系统程序,按需加载到内存中运行。了解这些,有助于了解整个烧写的过程。 在板子上,可以通过下面的命令,查看CPU的信息: [plain] cat /proc/cpuinfo 通过如下命令查看内存的信息: [plain] cat /proc/meminfo nand flash是需要分区的,每个分区中对应了Android系统烧写包中不同的image,比如:boot、system分区等。可以通过如下命令来查看nand flash 的分区情况: [plain] cat /proc/mtd # 查看分区状况 通常,nand flash包含了以下分区: 开机动画:用于在开机或者升级过程中显示在屏幕上的内容。 boot:用于Android系统的正常启动 recovery:用于Android系统进入recovery模式下,参见本文后续介绍。 misc:用于保存BCB的内容,参见本文后续介绍。 上文介绍了Android应用程序的启动过程,即应用程序默认Activity的启动过程,一般来说,这种默认Activity是在新的进程和任务中启动的;本文将继续分析在应用程序内部启动非默认Activity的过程的源代码,这种非默认Activity一般是在原来的进程和任务中启动的。 这里,我们像上一篇文章Android应用程序启动过程源代码分析一样,采用再上一篇文章Android 应用程序的Activity启动过程简要介绍和学习计划所举的例子来分析在应用程序内部启动非默认Activity的过程。 在应用程序内部启动非默认Activity的过程与在应用程序启动器Launcher中启动另外一个应用程序的默认Activity的过程大体上一致的,因此,这里不会像上文Android应用程序启动过程源代码分析一样详细分析每一个步骤,我们着重关注有差别的地方。 回忆一下Android应用程序的Activity启动过程简要介绍和学习计划一文所用的应用程序Activity,它包含两个Activity,分别是MainActivity和SubActivity,前者是应用程序的默认Activity,后者是非默认Activity。MainActivity启动起来,通过点击它界面上的按钮,便可以在应用程序内部启动SubActivity。 我们先来看一下应用程序的配置文件AndroidManifest.xml,看看这两个Activity是如何配置的:view plain 1. 2. Android系统的开机画面显示过程分析 分类:Android2012-07-0900:561252人阅读评论(39)收藏举报 好几个月都没有更新过博客了,从今天开始,老罗将尝试对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 内核编译选项可以参考在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)一文。 帧缓冲区硬件设备在内核中有一个对应的驱动程序模块fbmem,它实现在文件kernel/goldfish/drivers/video/fbmem.c中,它的初始化函数如下所示: 1/** Android系统完整的启动过程,从系统层次角度可分为Linux系统层、Android系统服务层、Zygote进程模型三个阶段;从开机到启动Home Launcher完成具体的任务细节可分为七个步骤,下面就从具体的细节来解读Android系统完整的初始化过程。 一、启动BootLoader Android 系统是基于Linux操作系统的,所以它最初的启动过程和Linux一样。当设备通电后首先执行BootLoader引导装载器,BootLoader是在操作系统内核运行之前运行的一段小程序。通过这段小程序初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境引导进入合适的状态,以便为最终调用操作系统内核准备好正确的运行环境。 而Linux系统启动时: 1)首先要加载BIOS的硬件信息,并获取第一个启动设备的代号 2)读取第一个启动设备的MBR的引导加载程序(lilo、grub等)的启动信息。 3)加载核心操作系统的核心信息,核心开始解压缩,并且尝试驱动所有的硬件设备。 ………… 在嵌入式系统中,通常不会有像BIOS那样的固件程序,因此整个系统的加载任务都是通过BootLoader完成的。 二、加载系统内核 Linux内核映像通常包括两部分代码,分别为实模式代码和保护模式代码。当BootLoader装载内核映像到代码段内存时,分别放置实模式代码和保护模式代码到不同的位置,然后进入实模式代码执行,实模式代码执行完成后转入保护模式代码。 实模式和保护模式的概念再次不做过多解释,读者可以自行查阅资料。 三、启动Init进程 当系统内核加载完成之后,会首先启动Init守护进程,它是内核启动的第一个用户级进程,它的进程号总是1。Init进程启动完成之后,还负责启动其他的一些重要守护进程,包括: Usbd进程(USB Daemon):USB连接后台进程,负责管理USB连接。 adbd 进程(Android Debug Bridge Daemon):ADB连接后台进程,负责管理ADB连接。 debuggerd 进程(Debugger Daemon) :调试器后台进程,负责管理调试请求及调试过程。 rild进程(Radio Interface Layer Daemon):无线接口层后台进程,负责管理无线通信服务。 四、启动Zygote进程 Init进程和一些重要的守护进程启动完成之后,系统启动Zygote 进程。Zygote 进程启动后,首先初始化一个Dalvik VM实例,然后为它加载资源与系统共享库,并开启Socket监听服务,当收到创建Dalvik VM实例请求时,会通过COW(copy on write)技术最大程度地复用自己,生成一个新的Dalvik VM实例。Dalvik VM实例的创建方法基于linux系统的fork原理。 Android系统启动流程(一)解析init 进程 前言 作为“Android框架层”这个大系列中的第一个系列,我们首先要了解的是Android系统启动流程,在这个流程中会涉及到很多重要的知识点,这个系列我们就来一一讲解它们,这一篇我们就来学习init进程。 1.init简介 init进程是Android系统中用户空间的第一个进程,作为第一个进程,它被赋予了很多极其重要的工作职责,比如创建zygote(孵化器)和属性服务等。init进程是由多个源文件共同组成的,这些文件位于源码目录system/core/init。本文将基于Android7.0源码来分析Init进程。 2.引入init进程 说到init进程,首先要提到Android系统启动流程的前几步: 1.启动电源以及系统启动 当电源按下时引导芯片代码开始从预定义的地方(固化在ROM)开始执行。加载引导程序Bootloader到RAM,然后执行。 2.引导程序Bootloader 引导程序是在Android操作系统开始运行前的一个小程序,它的主要作用是把系统OS拉起来并运行。 3.Linux内核启动 内核启动时,设置缓存、被保护存储器、计划列表,加载驱动。当内核完成系统设置,它首先在系统文件中寻找”init”文件,然后启动root进程或者系统的第一个进程。 4.init进程启动 讲到第四步就发现我们这一节要讲的init进程了。关于Android系统启动流程的所有步骤会在本系列的最后一篇做讲解。 3.init入口函数 init的入口函数为main,代码如下所示。 system/core/init/init.cpp int main(int argc, char** argv) { if (!strcmp(basename(argv[0]), "ueventd")) { return ueventd_main(argc, argv); } if (!strcmp(basename(argv[0]), "watchdogd")) { return watchdogd_main(argc, argv);android系统开机启动流程分析
Android 开机启动流程
分析Android 开机启动慢的原因
基于MT6752的 android 系统启动流程分析报告
linux内核启动 Android系统启动过程详解
Android开机启动流程样本
Android系统启动过程详解
Android SystemBar启动流程分析
AndroidL系统启动及加载流程分析
android开机启动流程简单分析
Android ninja 编译启动过程分析
android开机过程
Android系统启动升级流程
Android应用程序内部启动Activity过程(startActivity)的源代码分析
Android系统的开机画面显示过程分析
Android系统完整的启动过程
Android系统启动流程(一)解析init进程