第二章 BootROM与Bootloader:芯片级启动流程

2.1 从按下电源键说起

手机按下电源键的那一刻,发生了什么?

很多人以为第一个跑起来的是Linux内核。其实不是。在CPU真正执行任何代码之前,芯片内部有一个硬编码的微型程序——这就是BootROM。它固化在芯片的只读存储器里,你改不了,也删不掉。

我刚开始研究启动流程时,一直有个疑惑:为什么不能直接把Bootloader放在BootROM里?后来踩了坑才明白——BootROM太小了,通常只有几十KB。它只够做一件事:初始化最基本的硬件,然后加载Bootloader。

核心要点:BootROM是芯片上电后执行的第一个程序。它不可修改,负责初始化时钟、堆栈、存储器控制器,然后从指定存储介质加载Bootloader。

2.2 BootROM到底干了什么

说白了,BootROM就是个"启动引导器的小引导器"。它的工作流程非常固定:

  1. 上电复位——CPU从复位向量地址取指,这个地址指向BootROM
  2. 基础硬件初始化——设置时钟频率、关闭看门狗、初始化堆栈指针
  3. 检测启动介质——检查eMMC、UFS、SD卡等设备是否存在
  4. 加载Bootloader——从启动介质读取Bootloader到内部SRAM
  5. 跳转执行——验证签名后,跳转到Bootloader入口

嗯,这里要注意:BootROM阶段还没有DDR内存可用。它只能用芯片内部那点可怜的SRAM。所以Bootloader必须足够小,能塞进SRAM里。我记得在某个低端平台上,SRAM只有64KB,Bootloader稍微大一点就装不下,折腾了好几天才搞定。

2.3 Secure Boot:为什么需要它

你想想看,如果Bootloader可以被随意替换,那手机的安全性就形同虚设。恶意软件只要改掉Bootloader,就能在系统启动前植入后门。

Secure Boot就是为了解决这个问题。它的核心思想是:每一级启动程序都要验证下一级的签名

具体流程是这样的:

  • BootROM验证Bootloader的签名
  • Bootloader验证Boot.img的签名
  • Boot.img验证System分区的签名
  • ...以此类推,形成一条完整的信任链

个人经验:我在做某个平板项目时,Secure Boot验证一直失败。查了两天才发现,是Bootloader的签名密钥和BootROM里烧录的公钥不匹配。生产线上烧录密钥时出了差错。从那以后,我每次都会在项目初期就确认密钥对的匹配关系。

2.4 Bootloader的两阶段设计

Android的Bootloader通常分为两个阶段:

阶段 名称 存储位置 主要任务
第一阶段 SPL (Secondary Program Loader) eMMC的特定分区 初始化DDR、加载第二阶段
第二阶段 U-Boot / Little Kernel eMMC的Boot分区 初始化外设、加载内核

为什么搞这么复杂?因为BootROM能加载的程序大小有限。第一阶段SPL非常精简,它的唯一使命就是初始化DDR内存,然后把完整的U-Boot加载到DDR中。一旦U-Boot跑起来,整个系统就活了。

我曾经在一个项目里遇到过SPL加载U-Boot时CRC校验失败的问题。排查下来是eMMC的时序配置不对,导致读取数据出错。调整了eMMC的时钟频率后问题解决。这种问题在硬件调试阶段特别常见。

2.5 AB分区策略

你有没有遇到过系统升级到一半断电,手机变砖的情况?AB分区就是为了解决这个问题。

AB分区的核心思想很简单:准备两套系统分区。一套是当前运行的(Slot A),另一套是备用的(Slot B)。升级时只更新备用分区,升级完成后切换启动槽位。

关键机制:Bootloader会读取misc分区中的槽位信息,决定从A还是B启动。如果当前槽位启动失败,Bootloader会自动切换到另一个槽位。这就是"回滚保护"。

AB分区的典型布局:

  • boot_a / boot_b——内核镜像
  • system_a / system_b——系统文件
  • vendor_a / vendor_b——厂商驱动
  • misc——存储槽位信息和启动计数

避坑指南:我曾经在实现AB分区时犯过一个低级错误——两个槽位的fstab文件配置不一致。结果从Slot A切换到Slot B后,系统因为挂载不了data分区直接卡在开机动画。记住:两个槽位的分区配置必须完全对称。

2.6 Bootloader的完整启动流程

把上面这些串起来,完整的芯片级启动流程是这样的:

  1. CPU上电,从BootROM开始执行
  2. BootROM初始化基础硬件,检测启动介质
  3. BootROM从eMMC的Boot分区加载SPL到SRAM
  4. BootROM验证SPL的签名(Secure Boot)
  5. SPL初始化DDR内存
  6. SPL从eMMC加载U-Boot到DDR
  7. SPL验证U-Boot的签名
  8. U-Boot读取misc分区,确定启动槽位
  9. U-Boot从选定槽位加载内核和dtb
  10. U-Boot验证内核签名,然后跳转到内核入口

你看,每一步都有验证,每一步都有备份。这就是Android系统启动的第一道防线。

2.7 调试Bootloader的实用技巧

最后分享几个我在调试Bootloader时常用的方法:

  • 串口输出——Bootloader阶段还没有显示驱动,串口是唯一的调试手段。我习惯在关键节点加打印,比如"Loading SPL... OK"、"Verifying signature... OK"
  • GPIO点灯——如果串口都不好使,就用GPIO控制LED。不同颜色代表不同阶段,简单粗暴但有效
  • dump内存——在U-Boot命令行下,用md命令查看内存内容,确认数据是否正确加载
  • 模拟器测试——用QEMU模拟硬件环境,可以快速验证Bootloader逻辑,不用每次都烧录真机

我的习惯:每次拿到新板子,第一件事就是确认BootROM能不能正常加载SPL。如果这一步都过不了,后面全是白搭。先跑通最小系统,再逐步添加功能——这个原则在Bootloader开发中特别适用。

好了,芯片级启动流程就讲到这里。下一章我们会深入Linux内核的启动过程,看看内核是怎么接管系统的。到时候再聊。