2. 硬件初始化:ROM Code、SPL、ATF、U-Boot、Kernel、Rootfs 的启动顺序与职责
嵌入式系统上电后,不是直接跳进 Linux 内核的。它要经历一段「接力跑」——从芯片内部固化代码开始,一级一级把硬件初始化好,最后才把控制权交给操作系统。这个链条如果理不清,联调时你会一头雾水。
我个人习惯把启动链分成六个阶段:ROM Code → SPL → ATF → U-Boot → Kernel → Rootfs。每个阶段都有明确的职责边界。下面我逐个拆开讲。
2.1 ROM Code:芯片出厂就有的「第一行代码」
ROM Code 是芯片流片时固化在内部 ROM 里的程序。你改不了它,也删不掉它。它的任务很简单:从预定义的启动介质加载下一级程序。
上电后,CPU 从复位向量(通常是 0x00000000 或 0xFFFF0000)开始执行。ROM Code 会做几件事:
- 初始化最基本的时钟和 SRAM(通常是芯片内部 SRAM,几十 KB 到几百 KB)
- 读取启动引脚电平(eMMC、SD 卡、NAND Flash、SPI NOR 等)
- 从选中的介质读取 SPL 或 U-Boot 到内部 SRAM
- 校验签名(如果使能了安全启动)
- 跳转到 SPL 入口
2.2 SPL:把 DDR 初始化好,为 U-Boot 铺路
SPL(Secondary Program Loader)是 U-Boot 的「瘦身版」。为什么需要它?因为 ROM Code 能用的内部 SRAM 太小了,装不下完整的 U-Boot。SPL 的任务就是:初始化 DDR,然后把 U-Boot 从存储介质搬到 DDR 里。
SPL 的典型流程:
- 设置 CPU 工作模式(关闭中断、设置栈指针)
- 初始化时钟和 PLL(让 CPU 和 DDR 跑在目标频率)
- 初始化 DDR 控制器(时序参数、ODT、ZQ 校准)
- 从存储介质读取 U-Boot 到 DDR 指定地址
- 跳转到 U-Boot 入口
2.3 ATF:ARM 平台的「安全管家」
ATF(ARM Trusted Firmware)是 ARMv8 架构引入的固件层。它运行在 EL3(异常级别 3),是系统里权限最高的代码。它的职责包括:
- 初始化安全世界(Secure World)的硬件
- 设置电源管理(PSCI,Power State Coordination Interface)
- 提供安全监控调用(SMC)接口
- 跳转到非安全世界的 U-Boot
在启动链里,ATF 通常由 SPL 加载,或者由 U-Boot 的早期阶段加载。它执行完后,系统会降级到 EL2 或 EL1,把控制权交给 U-Boot。
关键点:ATF 不是必须的。如果你的芯片是 ARMv7 或更早架构,或者你不需要安全世界功能,可以跳过 ATF,让 SPL 直接跳 U-Boot。但 ARMv8 平台上,ATF 几乎是标配。
2.4 U-Boot:真正的「硬件初始化主力」
U-Boot 是启动链里最复杂的阶段。它负责初始化大部分外设,为内核准备好运行环境。U-Boot 的启动流程大致分两步:
第一阶段(汇编部分):
- 设置异常向量表
- 初始化 CPU 和缓存
- 设置栈指针
- 跳转到 C 语言入口
第二阶段(C 语言部分):
- 初始化串口(让你能看到打印信息)
- 初始化定时器
- 初始化 GPIO、I2C、SPI 等外设
- 初始化存储设备(eMMC、SD 卡、NAND)
- 初始化网络(如果支持网络启动)
- 解析设备树(DTB)
- 加载内核和设备树到 DDR
- 跳转到内核入口
2.5 Kernel:接管系统,启动进程
内核是启动链的「主角」。U-Boot 跳过来时,内核已经拿到了设备树和 initramfs(或根文件系统路径)。内核的启动过程:
- 解压自身(如果是压缩内核)
- 初始化架构相关代码(MMU、中断控制器、定时器)
- 解析设备树,注册平台设备
- 初始化驱动(按设备树里的顺序)
- 挂载根文件系统
- 启动 init 进程(PID 1)
内核启动时,打印信息会从 U-Boot 的串口继续输出。如果你看到 VFS: Mounted root 之类的信息,说明根文件系统挂载成功了。
2.6 Rootfs:用户空间的起点
Rootfs 是启动链的最后一环。内核启动 init 进程后,系统就进入了用户空间。Rootfs 的职责:
- 提供 init 程序(通常是 BusyBox 的 init 或 systemd)
- 执行初始化脚本(/etc/init.d/rcS 等)
- 挂载额外的文件系统(/proc、/sys、/dev、/tmp)
- 启动系统服务(网络、日志、看门狗等)
- 启动应用程序
在 Buildroot 里,Rootfs 的构建是自动化的。你只需要配置好包列表,Buildroot 会生成完整的根文件系统镜像。
2.7 启动链全景图
下面这张图把整个启动链串起来了。你可以看到每个阶段的加载者和被加载者,以及它们之间的跳转关系。
2.8 各阶段职责对比
为了方便你快速查阅,我把每个阶段的职责、运行位置和典型大小整理成了表格。
| 阶段 | 运行位置 | 典型大小 | 核心职责 |
|---|---|---|---|
| ROM Code | 内部 ROM | 4-32 KB | 加载 SPL,安全校验 |
| SPL | 内部 SRAM | 16-64 KB | 初始化 DDR,加载 U-Boot |
| ATF | DDR(安全区域) | 64-256 KB | 安全世界初始化,PSCI |
| U-Boot | DDR | 256 KB - 2 MB | 外设初始化,加载内核 |
| Kernel | DDR | 2-10 MB | 驱动加载,进程调度 |
| Rootfs | DDR / Flash | 1-100 MB | 用户空间初始化 |
总结一下:启动链的本质是「逐级放大」——ROM Code 用 KB 级的 SRAM 加载 SPL,SPL 初始化 MB 级的 DDR 后加载 U-Boot,U-Boot 再加载几 MB 的内核,内核最后挂载几十 MB 的根文件系统。每一级都在为下一级准备更大的运行空间。