27、Android系统启动流程:BootROM、BootLoader、Kernel、init、Zygote完整链路

各位同学,今天我们来聊聊Android系统从按下电源键到Launcher界面出现的完整链路。这条链路,说白了就是一部手机从“死”到“活”的全过程。我当年刚接触Android时,觉得这玩意儿就是个黑盒,直到自己动手调过几次启动时序,才真正理解每一环的设计意图。

你想想看,一个嵌入式设备,上电时CPU内部什么都没有,连内存都还没初始化。它怎么知道该执行哪段代码?怎么把Linux内核加载起来?又怎么启动Java虚拟机、运行App?嗯,这里面的门道,我们今天一条一条捋清楚。

1. BootROM:硬件固化的第一行代码

手机按下电源键,首先通电的是PMIC(电源管理芯片)。它给CPU供电,然后CPU内部的BootROM开始执行。BootROM是芯片出厂时固化在ROM里的代码,不可修改。它的任务只有一个:找到并加载BootLoader。

BootROM会尝试从eMMC、UFS、SD卡等存储介质中读取BootLoader。读取顺序由硬件引脚或efuse决定。我记得在某个项目中,客户要求从SD卡启动用于工厂烧录,结果BootROM死活读不到,最后发现是eMMC的引脚电平拉错了——硬件设计的小坑,排查了一整天。

核心要点:BootROM是硬件固化代码,负责初始化最基本的硬件(如时钟、存储控制器),然后加载BootLoader到SRAM中执行。

2. BootLoader:硬件初始化的“二传手”

BootLoader被加载到SRAM后,开始执行。它比BootROM复杂得多,主要做三件事:

  • 初始化DDR内存(DRAM控制器、时序校准)
  • 建立内存映射,为加载内核做准备
  • 从存储中读取Linux内核镜像(boot.img)到内存

Android设备常用的BootLoader是Little Kernel(LK)或U-Boot。我个人习惯把BootLoader看作一个“跳板”——它本身不复杂,但必须稳定。我曾经遇到过一个问题:某款芯片的DDR初始化时序参数写错了,导致内核加载到一半就崩溃。排查时发现是BootLoader里DDR配置表的寄存器值少了一位,嗯,这种问题用JTAG调试才抓得到。

避坑指南:我曾经在调试启动时,发现BootLoader加载内核后总是卡在“Uncompressing Linux...”这一步。后来发现是内核镜像的gzip校验和不对,因为烧录工具把镜像截断了。建议大家在量产前,一定要做一次完整的启动压力测试。

3. Kernel:从汇编到C的世界

BootLoader跳转到内核入口后,内核开始执行。内核启动分两个阶段:

  1. 汇编阶段(head.S):设置页表、使能MMU、建立C语言运行环境
  2. C语言阶段(start_kernel):初始化调度器、内存管理、文件系统、驱动模型等

内核启动的最后一步,是挂载根文件系统(rootfs),然后执行第一个用户空间进程——init。这里有个关键点:内核必须找到init程序,否则会panic。我记得有一次,因为ramdisk里缺少init可执行文件,内核启动后直接打印“Kernel panic - not syncing: No init found”,当时还以为是内核配置错了,查了半天才发现是打包脚本漏了文件。

内核启动关键日志:
[    0.000000] Booting Linux on physical CPU 0x0
[    0.000123] Machine model: Qualcomm Technologies, Inc. MSM8998
[    0.500456] Freeing unused kernel memory: 1024K
[    0.500789] Run /init as init process

4. init进程:用户空间的“一号进程”

init是Linux内核启动的第一个用户空间进程,PID为1。它的核心工作是解析init.rc脚本,启动系统服务。Android的init与标准Linux的init不同,它使用自己的.rc语法,支持service、on、trigger等关键字。

init.rc中定义了大量的系统服务,比如servicemanager、surfaceflinger、media等。但最重要的,是它启动了Zygote。你想想看,如果没有Zygote,每个App启动时都要重新加载一遍Java虚拟机,那效率得多低?

个人经验:我建议大家在调试启动问题时,多关注init.rc中的“on boot”和“on property:”触发器。有一次,某个服务启动失败,是因为它依赖的property在init.rc中设置得太晚,导致服务启动时property还没就绪。调整一下触发顺序就解决了。

5. Zygote:Java世界的“孵化器”

Zygote是Android Java世界的起点。它由init进程启动,启动时做两件事:

  • 预加载Java核心类库和资源(如android.jar、主题资源)
  • 创建虚拟机实例,并打开Socket监听

之后,所有App进程都由Zygote fork而来。fork时,子进程会继承Zygote的虚拟机实例和预加载资源,这样App启动就快多了。我经常跟团队说:Zygote就是Android的“母体”,所有App都是它的“子体”。

Zygote启动后,会执行SystemServer的main方法。SystemServer负责启动系统服务,比如ActivityManagerService、WindowManagerService、PackageManagerService等。这些服务都跑在system_server进程中。

Zygote启动流程简化图: BootROM 硬件固化,加载BootLoader BootLoader 初始化DDR,加载内核 Kernel 初始化驱动,挂载rootfs init 解析init.rc,启动服务 Zygote 预加载类库,fork App 启动阶段说明 1. BootROM: 硬件固化,不可修改 2. BootLoader: 可定制,初始化DDR 3. Kernel: 驱动加载,挂载文件系统 用户空间启动 4. init: 一号进程,解析.rc脚本 5. Zygote: Java虚拟机,预加载 6. SystemServer: 系统服务

6. 完整链路总结

从按下电源键到Launcher显示,整个链路可以概括为:

阶段 执行内容 关键输出
BootROM 硬件初始化,加载BootLoader BootLoader镜像加载到SRAM
BootLoader DDR初始化,加载内核 内核镜像加载到DDR
Kernel 驱动初始化,挂载rootfs init进程启动
init 解析init.rc,启动系统服务 Zygote进程启动
Zygote 预加载Java类库,fork App SystemServer启动

我个人觉得,理解这条链路的关键在于“分层”思维。每一层只做自己该做的事,然后交给下一层。BootROM只管加载BootLoader,BootLoader只管加载内核,内核只管启动init……这样设计,每一层都可以独立升级和调试。

注意事项:在实际项目中,启动链路的每个环节都可能成为瓶颈。比如BootLoader的DDR初始化如果太慢,整个启动时间就会增加几百毫秒。我曾经在优化启动速度时,把BootLoader里不必要的延时循环去掉,直接省了200ms。所以,如果你在做启动优化,建议从BootROM开始逐段测量时间。

好了,关于Android系统启动的完整链路,我们就聊到这里。下一章我们会深入Zygote的fork机制和SystemServer的启动细节,到时候再细聊。


公众号:蓝海资料掘金营,微信deep3321