二、安卓系统启动过程剖析
1.完整的安卓系统启动流程
从上图可以看出:安卓的启动流程是
开机加电--------执行BootLoader---------启动Linux内核--------启动安卓系统相关服务-----HomeLauncher启动,到这里,安卓的桌面就显示出来了,启动过程完成。
下面将安卓的启动分成三部分来介绍:
1. BootLoader 的启动
主要的功能是将操作系统启动的代码拷贝到内存合适的位置,并跳转到特定的位置开始执行操作系统的启动代码。
2. Linux内核的启动
完成linux的启动,建立一个完整的Linux运行环境
3. 安卓init进行的启动
在Linux内核提供的服务的基础上,初始化安卓的各种数据结构和服务,对上层的应用程序提供服务。
系统启动大致可分为一下几个阶段:
bootloader---初始化、从Flash读取Kernel镜像及一些必须的配置信息,引导kernel启动
linuxkernel启动linux内核
init进程启动
init进程读取init.rc启动必要的daemon程序,如:adbd、vold、netd、等
init进程启动servicemanager---随后详细分析其过程
init进程启动zygote ---随后详细分析其过程
· JAVA部分的Service启动
init进程启动mediaserver---多媒体本地服务启动
安卓架构图:
2.Bootloader启动
BootLoader就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。
安卓手机的OEM厂商很多都是没有开放Bootloader的,所以现在很多的刷机包都会列出试用的机型,因为只有允许解锁BootLoader的手机才能刷入第三方的Rom,否则只能刷入官方的Rom.
BootLoader通常包含处理器厂商开发的一小段上电引导程序,BootLoader通常是先执行一段汇编代码,然后是C代码。汇编代码一般用来直接操作处理器,内存,初始化硬件环境,C代码用来初始化各种需要的数据结构,获得系统的各种信息保存起来等。
3.Linux内核的启动
3.1 linux的汇编启动阶段
1. 确定 processor type
2. 确定 machine type
3. 手动创建页表
4. 调用平台特定的cpu setup函数,设置中断地址,刷新Cache,开启Cache
(在struct proc_info_list中,in proc-arm920.S)
5. 开启mmu I、D cache ,设置cp15的控制寄存器,设置TTB寄存器为0x30004000
6. 切换数据(根据需要赋值数据段,清bss段,保存processor ID 和 machine type
和 cp15的控制寄存器值)
7. 最终跳转到start_kernel
(在__switch_data的结束的时候,调用了 b start_kernel)
3.2 Linux的C启动阶段
经过前面的过程,将会进入init/Main.c中的start_kernel()函数去继续执行
1. printk(linux_banner)打印内核的一些信息,版本,作者,编译器版本,日期等信
息。
2. 接下来执行是一个及其重要的函数setup_arch(),主要做一些板级初始化,cpu初始
化,tag参数解析,u-boot传递的cmdline解析,建立mmu工作页表(memtable_init),初始化内存布局,调用mmap_io建立GPIO,IRQ,MEMCTRL,UART,及其他外设的静态映射表,对时钟,定时器,uart进行初始化, cpu_init():{ 打印一些关于cpu的信息,比如cpu id,cache 大小等。另外重要的是设置了IRQ、ABT、UND三种模式的stack空间,分别都是12个字节。最后将系统切换到svc模式}。
3. sched_init():初始化每个处理器的可运行队列,设置系统初始化进程即0号进程。
4. 建立系统内存页区(zone)链表 build_all_zonelists()。
5.printk(KERN_NOTICE "Kernel command line: %s\n", saved_command_line);打印出从uboot传递过来的command_line字符串,在setup_arch函数中获得的。
6. parse_early_param(),这里分析的是系统能够辨别的一些早期参数(这个函数甚至可以去掉,__setup的形式的参数),而且在分析的时候并不是以setup_arch(&command_line)传出来的command_line为基础,而是以最原生态的saved_command_line为基础的。
7. parse_args("Booting kernel", command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
对于比较新的版本真正起作用的函数,与parse_early_param()相比,此处对解析列表的处理范围加大了,解析列表中除了包括系统以setup定义的启动参数,还包括模块中定义的param参数以及系统不能辨别的参数。
__start___param是param参数的起始地址,在System.map文件中能看到
__stop___param - __start___param是参数个数
unknown_bootoption是对应与启动参数不是param的相应处理函数(查看parse_one()就知道怎么回事)。
8. 在前面的setup_arch-àpaging_init-à memtable_init函数中为系统创建页表的时候,中断向量表的虚地址init_maps,是用alloc_bootmem_low_pages分配的,ARM规定中断向量表的地址只能是0或0xFFFF0000,所以该函数里有部分代码的作用就是映射一个物理页到0或0xFFFF0000。
trap_init函数做了以下的工作:把放在.Lcvectors处的系统8个意外入口跳转指令搬到高端中断向量0xffff0000处,再将__stubs_start到__stubs_end之间的各种意外初始化代码搬到0xffff0200处,等。
9. init_IRQ()
初始化系统中所有的中断描述结构数组:irq_desc[NR_IRQS]。接着执行init_arch_irq函数,该函数是在setup_arch函数最后初始化的一个全局函数指针,指向了smdk2410_init_irq函数(in mach-smdk2410.c),实际上是调用了s3c24xx_init_irq函数。在该函数中,首先清除所有的中断未决标志,之后就初始化中断的触发方式和屏蔽位,还有中断句柄初始化,这里不是最终用户的中断函数,而是do_level_IRQ或者do_edge_IRQ函数,在这两个函数中都使用过__do_irq函数来找到真正最终驱动程序注册在系统中的中断处理函数。
10. softirq_init():内核的软中断机制初始化函数。
12. console_init():
初始化系统的控制台结构,该函数执行后调用printk函数将log_buf中所有符合打印级别的系统信息打印到控制台上。
13. profile_init()函数
/* 对系统剖析做相关初始化, 系统剖析用于系统调用*/
//profile是用来对系统剖析的,在系统调试的时候有用
//需要打开内核选项,并且在bootargs中有profile这一项才能开启这个功能/*
profile只是内核的一个调试性能的工具,这个可以通过menuconfig中profiling support打开。
14. vfs_caches_init()
该函数主要完成的是文件系统相关的初始化,cache、inode等高速缓存的建
立,在mnt_init()函数中有注册并初始化sysfs、rootfs文件系统,这里只是在内存中建立他们的架构,创建了超级块,并没有真正挂载上去。关于这个rootfs需要说明的是,这个文件系统生命期更加短暂的,为什么?之前说的ramdisk大家是否还记得,ramdisk即将在后面释放到内存空间,来代替这里的rootfs出现在根目录之下,而这个rootfs则退居二线,隐藏在一个二级目录中。本来在非android的系统上,这个ramdisk也是一个暂时的文件系统,之后也会被真正的yaffs2之类的文件系统替换。不过呢,在android上,这个ramdisk还是挂载在根目录下的,只是将system、userdata等真实文件系统挂载了对应的二级目录下。
关于这部分ramdisk内容,有兴趣的下来可以继续探讨。
15. mem_init():
最后内存初始化,释放前边标志为保留的所有页面,这个函数结束之后就不能再使
用alloc_bootmem(),alloc_bootmem_low(),alloc_bootmem_pages()等申请低端内存的函数来申请内存,也就不能申请大块的连续物理内存了。
16. 中间还省略了很多内容,涉及到很多东西,这里也没有时间详细讨论,有兴趣的自
己研究代码吧!下面直接跳到start_kernel()函数的最后的一个重要函数:rest_init()。
17. rest_init函数创建了两个线程之后,自己调用cpu_idle()函数隐退了。
创建的第一个线程,习惯上我们将其叫做1号内核线程,第二个线程叫2号内核线程,因为创建它们的父进程叫0号启动进程。
说明一下:2.6.14的内核这里只创建了一个内核线程叫init线程,而上面创建两
个线程的内核版本至少都是2.6.2x了,所以为了后面能和android的启动接上,所以这里开始linux转到2.2.29去。
static noinline void __init_refok rest_init(void) __releases(kernel_lock)
{
int pid;
…
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);