声明
- 前阶段在项目中涉及到了Android系统定制任务,Android系统定制前提要知道Android系统是如何启动的。
- 本文参考了一些书籍的若干章节。
- 本文使用的代码是LineageOS的cm-14.1,对应Android 7.1.2,可以参考我的另一篇博客:cm-14.1 Android系统启动过程分析(1)-如何下载Nexus5的LineageOS14.1(cm-14.1)系统源码并编译、刷机。
1. Android系统镜像
可以参考磁篇:Android 系统的分区和文件系统(1)–Android分区的大概框架
关于系统启动,关键的镜像有6个,这些镜像的刷入工具为fastboot:
镜像 | 功能 |
---|---|
BootLoader镜像 | 常见的如uboot,复制系统刚上电,内核启动前的基础硬件/软件初始化工作 |
boot镜像 | 其中为可自解压的linux内核压缩包和RAM disk根文件系统 |
recovery镜像 | 包含Linux内核,负责镜像更新、刷机 |
system镜像 | 存放的是一个完整的Android系统,其中除了谷歌提供的二进制可执行文件和框架 |
vendor镜像 | 厂商或运营商提供的类似的东西 |
data镜像 | 存放的是“默认出厂设置”的数据文件,它们是/system分区中程序正常运行所必需的文件。当手机需要重置到“出厂状态”时,只需把这个分区镜像刷写到/data分区,覆盖掉原来的数据即可。 |
2. 嵌入式视角BootLoader的启动过程
以常用的U-Boot 启动内核的过程为例,其可以分为两个阶段:
第一阶段的功能有:硬件设备初始化,加载U-Boot第二阶段代码到RAM空间,设置栈(包括设置指针和置空 BSS 段),跳转到第二阶段代码人口。
第二阶段的功能有:调用的函数分别为硬件配置board_init,时钟初始化timer_init,环境变量初始化env_init,串口初始化init_baudrate、serial_init、console_init_f,配置RMA dram_init,并且使用display_dram_init显示RAM的大小,NOR Flash初始化flash_init,NAND Flash初始化nand_init。
3. 内核启动过程
Android内核的启动过程和Linux的没什么太大区别。不过,坏消息是,后者经常会发生一些变动。比如,内核升级时也会经常添加或删除一些组件,而且不同平台之间变化也很大。因此这一节中只对 3.x 内核的启动过程做一个大致的描述,你或许还需要去参考某个特定设备的内核源码树中的代码。
Boot Loader的作用是将经过压缩的内核Image和RAM disk加载到内存中去,一旦加载完毕,系统的控制权就会被转移到zlmage的入口点上去。在这个时刻,内核还处于压缩状态,所以实现在arch/architecture/boot/compressed/head.S文件中,入口点上的指令就是去调用decompress kernel函数,这个函数会在屏幕上显示出一条我们熟悉的“Uncompressing Linux…done,booting the kernel消息,并在解压完内核之后,把控制权交给“真正的”入口点函数。这是个与处理器体系结构紧密相关的函数,是用汇编的方式写就的。接下来,还要在底层执行设置MMU(内存管理单元)和页表(方便切换到虚地址上去)等操作,最后才能把系统的控制权交给内核的main函数start_kernel()
start kerne()这个函数与处理器的类型无关,所以它被放在了init/main.c中实现。从某种意义上说,这个函数写得相当好,其中几乎没有使用任何变量,大多数的启动工作都是通过调用其他函数的方式完成的。start_kernel()先用各个专门的函数去初始化所有重要的框架,然后再去调用rest_init()函数(这个函数的作用就是去初始化剩下的一切东西)。这个函数会生成一个 kermel_init 线程,该线程负责初始化各个不同的子线程。
因为有这么多的子系统要初始化,要是没什么控制机制,内核代码也就肯定会变得极其冗长。initcall机制提供了一个非常漂亮的解决方案,它定义了下表的8个初始化级别,使得kernel init线程可以(通过do_basic_setup()中的do_initcalls()函数)依次调用它们。
这一机制的设计思想与经典的用户模式下的init进程的“运行级”(runlevel,init 进程用它把各个子系统的启动脚本划分在不同的组中)的概念是非常相似的。initcalls模仿这一思想的方式是:要求各个子系统在注册它们的初始化函数时,通过level_initcall 宏,说明自己应该被放入哪个级别。在系统启动过程中,kernel_init就会依次初始化各个级别中注册的系统。执行到哪个级别,就会调用注册在该级别上的各个子系统的初始化函数,当所有级别都被处理完之后整个内核的初始化也就完成了。
4 Linux内核启动init进程
内核初始化结束后,会加载根文件系统并启动1号进程init进行Native系统服务的启动,请看后续章节。