第二讲:开机启动的起点——BootROM与BootLoader的职责

各位同学,今天我们来聊聊Android启动流程的源头。

很多人觉得开机就是从按下电源键开始。其实不对。按下电源键之前,芯片就已经在干活了。嗯,准确说,是芯片内部的一个固化程序——BootROM,它才是真正的起点。

2.1 BootROM:芯片出厂就写死的“第一行代码”

BootROM是什么?说白了,它是固化在SoC内部的一块只读存储器里的程序。芯片一上电,CPU的第一条指令就从这里取。

我记得刚入行时,有个同事问我:“BootROM能不能改?”我笑了笑说:“你要是能改,得先找台能刻录芯片掩模的设备,而且得花几百万美金重新流片。”他听完就沉默了。

BootROM的核心职责:

  • 初始化最基本的硬件(时钟、电源、内存控制器)
  • 从预定义的启动介质读取BootLoader
  • 验证BootLoader的签名(安全启动流程)
  • 跳转到BootLoader的入口地址

你想想看,BootROM的代码量通常只有几十KB。为什么这么小?因为空间太金贵了。芯片面积就是成本,BootROM占用的每一平方毫米都得精打细算。

2.2 BootLoader:承上启下的“二传手”

BootLoader从BootROM手里接过控制权后,它的任务就清晰了:加载Linux内核。

但这里有个坑。BootLoader本身也分阶段。以我熟悉的U-Boot为例,它通常分为两个阶段:

阶段 存放位置 主要工作
SPL(Secondary Program Loader) 片上SRAM或固定的Flash区域 初始化DRAM,加载完整的U-Boot
U-Boot完整版 DRAM中运行 初始化外设,加载内核到内存,启动内核

为什么分两阶段?因为BootROM能初始化的内存太小了。SPL先把DRAM搞起来,然后才能把完整的U-Boot从Flash里搬进去。我曾经在一个低端芯片项目上踩过这个坑——SPL代码写得太臃肿,导致片上SRAM装不下,最后不得不砍功能。嗯,从那以后我写SPL都格外小心。

2.3 引导加载Linux内核的完整链路

好,我们串起来看一遍完整流程:

  1. 上电复位:CPU从0x00000000(或复位向量)取指,执行BootROM
  2. BootROM初始化:设置栈指针、初始化时钟、配置基本GPIO
  3. 读取启动设备:从eMMC、NAND Flash或UART等介质读取SPL
  4. SPL执行:初始化DRAM控制器,把U-Boot完整版加载到DRAM
  5. U-Boot执行:初始化更多外设(USB、网络、显示),解析设备树
  6. 加载内核:从分区读取zImage/Image,放到指定内存地址
  7. 跳转到内核:设置好参数(ATAG或设备树),执行内核入口函数

避坑指南:我曾经遇到一个启动失败的问题,查了三天发现是BootLoader传给内核的设备树地址不对。内核启动时访问了错误的内存区域,直接挂死。所以,设备树地址一定要和内核编译时指定的地址一致

2.4 关键数据结构:ATAG与设备树

BootLoader怎么把硬件信息告诉内核?早期用ATAG,现在主流是设备树(Device Tree)。

ATAG是个链表结构,每个节点包含一个tag ID和对应的数据。比如:

// ATAG示例(简化版)
struct tag {
    uint32_t tag_id;    // 例如 ATAG_MEM = 0x54410002
    uint32_t size;      // 数据大小
    union {
        struct tag_mem32 mem;  // 内存信息
        struct tag_cmdline cmdline; // 内核启动参数
    } data;
};

设备树就灵活多了。它是一个树形结构,用.dts文件描述硬件。BootLoader把它编译成.dtb二进制,然后传给内核。

我个人习惯用设备树,原因很简单:可维护性高。ATAG改个内存大小都得改BootLoader代码,设备树改个.dts文件重新编译就行。我在一个项目上把ATAG迁移到设备树后,硬件适配的工作量直接减了一半。

2.5 安全启动:BootROM的“守门员”角色

现在Android设备基本都要求安全启动。BootROM会验证BootLoader的签名,BootLoader再验证内核的签名。一环扣一环,形成信任链。

注意:如果BootROM的验签密钥被泄露,整个信任链就崩塌了。所以芯片厂商对BootROM的密钥管理极其严格。我听说某厂商的BootROM密钥是分三段由三个不同的人保管的,每次烧录都得三个人同时到场。

安全启动的流程大致如下:

  • BootROM读取BootLoader的签名和证书
  • 用内置的公钥验证证书合法性
  • 用证书中的公钥验证BootLoader的哈希值
  • 验证通过才跳转,否则进入下载模式或直接死机

你可能会问:“那刷机怎么办?”嗯,这就是为什么有“解锁BootLoader”这个操作。解锁后,BootROM会跳过签名验证,或者使用用户提供的公钥。但代价是设备的安全等级会降低,有些银行App会检测到并拒绝运行。

2.6 小结

这一章我们聊了BootROM和BootLoader的职责。说白了,它们就是Android启动流程的“开路先锋”。没有它们,Linux内核连内存都访问不了,更别提启动系统了。

下一章,我们会深入Linux内核的启动过程。到时候你会看到,内核从汇编代码到C代码的切换,以及init进程是如何诞生的。嗯,那才是真正的重头戏。

对了,如果你在项目中遇到BootLoader相关的奇怪问题,不妨先检查一下设备树地址和签名验证。这两个地方出问题的概率最高。我曾经就栽在设备树地址上,折腾了整整一个周末……