Android 的底层基于 Linux Kernel,因此从启动流程来看,先启动 Linux Kernel,然后才启动 Android Framework,最后进入应用程序 Launcher,也就是看到的主界面。因为这一流程复杂且冗长,所以分为三篇文章来介绍,其实也就是 Android 启动的三个阶段。第一个阶段是 Linux 启动过程,包含上电后从 Bootrom 开始,到进入 BootLoader,然后运行 Linux Kernel。第二阶段是 Android Framework 的启动过程,包含启动 Zygote,System Server,servicemanager 等 Android 核心服务进程。第三阶段为启动第一个 Android 应用程序 Launcher 的过程。
Android 支持多种启动模式,主要有正常模式(normal mode),恢复模式(recovery mode),快速启动模式(fastboot mode)等,其中后两者是刷机模式。通过以下方式可以快速进入刷机模式:
adb reboot recovery 进入 recovery 模式
adb reboot bootloader 进入 fastboot 模式
第一阶段又可以分成三个小步骤(有的资料会把步骤1和步骤2作为一个整体,统称为 Bootloader)。
1. 机器上电,进入Bootrom
Bootrom 是固化在芯片中的一小段程序,主要功能是上电时完成硬件自检,然后从固定分区加载 Bootloader。严格来说,由于 Bootrom 需要尽可能精简,一般只会加载 Bootloader 头部一小段镜像内容,再由这一小段镜像加载剩余 Bootloader。
Bootrom 的功能相当于 PC 上的 BIOS,Bootloader 的功能相当于 PC 上的 GRUB,一般每个 Android 厂商都会根据实际需要对 Bootloader 进行客制化。此外,一些手机厂商会锁住 BootLoader,这样确保用户只能使用官方的系统。如果想要运行第三方的 ROM,那么就需要对 bootloader 进行解锁。
2. Bootloader初始化软硬件环境
Bootloader 是在进入 Linux Kernel 之前运行的程序。顾名思义,Bootloader 可以理解成 boot 和 loader 的混合体。
Bootloader 没有统一标准,有很多种实现方式,常用的实现就有 Uboot, GRUB, LILO 等,Android 采用的 Bootloader 实现方式是 Uboot。Uboot 功能非常强大,基本支持所有通用的硬件体系结构。看过 Uboot 的代码就可以知道,整个启动过程绝对可以称得上曲折艰难。如果编译 Uboot 时将 bootdelay 参数设置为大于0,则可以停留在 Uboot 中执行命令,比如从其他载体中手动加载 Kernel 运行,当然也可以手动升级其他分区内容。
boot 原来的意思是靴子,它来源于一句西方谚语:
pull oneself up by one’s bootstraps
中文意思是“拽着鞋带把自己拉起来”。这个比喻在 Android 机器启动过程中确实也生动形象。因为 Bootloader 镜像一般都是烧写在 NAND Flash 中,而 NAND Flash 中一般是不能运行程序的。Bootloader 需要自己把镜像从 Flash 加载进内存中,然后运行起来。
Flash 分为 NAND Flash 和 NOR Flash,是市场上两种主要的非易失闪存介质。由于工艺原理不同,两者区别很大。总的来说,NAND Flash 容量大,价格便宜,主要用于存储数据。而NOR Flash 则相反,主要用于存储代码。NAND Flash 只能顺序访问,访问速度相对较慢,而 NOR Flash 可以随机访问,访问速度快,在一些单片机中会被当成 RAM 使用。
loader 的意思是加载,实际上是初始化好 Kernel 运行时需要的设备环境,然后把 Kernel 加载进内存运行。
3. 启动 Linux Kernel
这一阶段和标准 Linux Kernel 启动过程基本一致。Kernel 加载进内存后会进行自解压。自解压完成后,继续进行一些平台相关的初始化,然后开始 start_kernel 函数,它的实现代码位于 init/main.c 中。
start_kernel 函数会进行一些软件相关的初始化,解析 bootloader 传过来的启动参数,最后调用 rest_init 函数启动 init 进程。
static noinline void __init_refok rest_init(void)
{
......
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
......
}
rest_init 函数会调用 kernel_thread 函数克隆出 init 子进程和 kthreadd 子进程。其中 init 子进程是 Linux 用户空间的1号进程,担负着在第二阶段启动 Android Framework 的重要职责。我们知道 kernel_thread 的第一个参数是进程执行的函数体,因此 init 进程会开始执行 kernel_init 函数。
static int __ref kernel_init(void *unused)
{
kernel_init_freeable();
......
if (ramdisk_execute_command) {
if (!run_init_process(ramdisk_execute_command))
return 0;
pr_err("Failed to execute %s\n", ramdisk_execute_command);
}
......
}
其中,在 kernel_init_freeable 函数中会将 ramdisk_execute_command 变量赋值为 /init,如下所示:
static noinline void __init kernel_init_freeable(void) {
......
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
......
}
最终,run_init_process 函数会执行 /init 程序,其源代码位于 /system/core/init/init.c 中。
static int run_init_process(const char *init_filename)
{
argv_init[0] = init_filename;
return do_execve(init_filename,
(const char __user *const __user *)argv_init,
(const char __user *const __user *)envp_init);
}
启动 init 进程的过程虽然比较冗长,但是基本流程和其他进程创建过程一样,都是先调用 fork 函数克隆出子进程,然后在子进程中调用 exec family 函数运行应用程序。
至此,第一阶段的 Linux 启动过程就完成了。下面开始 init 进程中启动 Android Framework 的过程。
参考学习资料:
1. Booting Linux kernel using U-Boot