前言
来了解一下Android 固件和内核启动过程是怎么样的
固件启动过程
手机中的固件,就相当于 PC 机里的 BIOS(或者现在的 EFI),其主要部件是一个由硬件制造厂商提供的 boot ROM。
顾名思义,boot ROM 是记录在只读 (read-only-memory)部件中的所以它一般都很小,只记录了一小段在手机刚一加电到加载 sbl之前必须要先执行的启动代码。它的作用是初始化其他的硬件部件,使它们处于至少是可以使用的状态。然后,boot ROM 就会加载sbl(secondary boot loader,次级启动加载器)并把系统控制权交给 sbl。sbl是个软件,所以它不受硬件 ROM 容量的限制,可以记录更多的数据,所以也能去执行更复杂的初始化任务(比如,显示一个启动画面)。
手机里的处理器和 PC 机里的还不一样,它并不是一个单独的CPU(比如说一块Intel或者ARM 处理器),而是一个完整的芯片组 (SoC,System-on-Chip)。事实上,这也就意味着有好几个处理器在同时工作,而应用处理器 (application processor)只是其中之一。比如说,高通的“骁龙”处理器中就至少含有4 个子处理器:RPM(资源/电源管理)、Krait(应用处理器)、Adreno(图形处理器–GPU’)、和Hexagon (数字签名处理器–DSP,Digital Signal Processor)。
所以MSM芯片组的启动也是一个特别冗长的过程,先执行 boot ROM中的PBL(初级启动加载器primary boot loader),然后再加载执行 sbl(次级启动加载器)。
而这个sbl本身又可以分为三个阶段(sbl1-sbl2-sbl3),各个 sl 阶段加载和验证下一阶段代码的过程又是一段令人眼花缭乱的复杂操作,其中还涉及了 rpm和 tz (ARM 可信区域)中的代码。之后应用处理器才能启动其他的组件,执行 aboot 中的代码,一直要到到这儿,Android 的 Boot Loader 才算是正式登场。
fastboot 协议
Android 系统的 Boot Loader 大多都支持“fastboot”协议,谷歌把它作为 Android 的一部分,放在官网上供下载。fastboot 是一个简单的、基于文本的协议,这意味着它能使用在手机/平板电脑与电脑连接的 USB 信道上。从性能角度讲,这个协议并不快 (比如它是同步的协议),协议名中的“fast”应该是指它实现起来很容易,所以实现起来也很快。
fastboot 协议在电脑和手机之间传递协议消息的过程
fastboot的默认命令
使用 fastboot
Android SDK 中提供的这些命令组成了一个简单但却完备的协议。为了确认你的设备的Boot Loader 是不是支持 fastboot,你首先需要让设备在启动到 Boot Loader 阶段时停下来。要让设备在这时停下,除了在启动时按下规定的组合键 (具体是哪个组合键,会因设备的不同而略有差异)之外,你也可以使用“adb reboot bootloader”命令。这样你的设备就会重启到 Boot Loader然后,如果它支持 fastboot 的话,在你输入“fastboot devices”命令时,就会看到设备的序列号输出在屏幕上。
这时,手机/平板电脑的屏幕上显示的应该是 BootLoader 的 UI,你只需使用物理按键就能对 Boot Loader 菜单进行操作(通常用音量增加/降低键上下切换当前选中项,电源键选择执行当前选中项)。你也可以用上图的表 中给出的任何一条命令操作你的手机,不过如果你不理解所输入命令的含义的话,乱输命令可能会破坏手机/平板电脑上的数据。比如,你可以输入getvarall
命令,列出 Boot Loader 中的所有变量。在不同的手机上,这条命令的输出结果将会是不一样的。
内核启动过程
Android 内核的启动过程和 Linux 的没什么太大区别。不过,坏消息是,后者经常会发生一些变动。比如,内核升级时也会经常添加或删除一些组件,而且不同平台之间变化也很大。
Boot Loader 的作用是将经过压缩的内核 zImage 和 RAM disk 加载到内存中去。一旦加载完毕,系统的控制权就会被转移到 zlmage 的入口点上去。在这个时刻,内核还处于压缩状态,所以实现在 arch/architecture/boot/compressed/head.S
文件(如果是一台x86或x64 设备对应的文件则是 head 32.S
或 head 64.S
)中,入口点上的指令就是去调用 decompress kermel函数,这个函数会在屏幕上显示出一条我们熟悉的Uncompressing Linux...done,booting the kernel
消息,并在解压完内核之后,把控制权交给“真正的”入口点函数。
接下来,还要在底层执行设置 MMU(内存管理单元)和页表(方便切换到虚地址上去) 等操作,最后才能把系统的控制权交给内核的main函数一start kernel0)。
start kernel()这个函数与处理器的类型无关,所以它被放在了init/main.c 中实现。其中几乎没有使用任何变量,大多数的启动工作都是通过调用其他函数的方式完成的。
start kernel()先用各个专门的函数去初始化所有重要的框架 (framework),然后再去调用 rest_init()函,顾名思义,这个函数的作用就是去初始化剩下的一切东西。这个函数会生成一个 kerel init 线程,该线程负责初始化各个不同的子线程。
因为有这么多的子系统要初始化,要是没什么控制机制,内核代码也就肯定会变得极其冗长。initcall 机制提供了一个非常漂亮的解决方案:,它定义了下表的 8 个初始化级别,使得kernel init 线程可以(通过 do basic setup()中的 do initcalls())函数)依次调用它们:
内核启动线程执行完毕之后,就会转入用户态,用 PID1启动/init。
参考
《最强Android 书:架构大部析》