2. 嵌入式系统启动流程:从复位向量到操作系统启动的完整流程
嵌入式系统的启动过程是一个从硬件复位到操作系统内核运行的完整链条。理解这一流程是 Bootloader 开发的核心基础。本章将详细拆解启动的各个阶段,并明确 ROM 代码、一级 Bootloader、二级 Bootloader 的角色划分。
2.1 启动流程总览
典型的嵌入式系统启动流程可分为以下五个主要阶段:
- 上电复位与硬件初始化:CPU 从复位向量地址取指,执行固化在 ROM 中的启动代码。
- ROM 代码执行:完成最底层的硬件配置,加载一级 Bootloader 到内部 SRAM。
- 一级 Bootloader (SPL/MLO):初始化 DDR 等关键外设,加载二级 Bootloader 到 DDR。
- 二级 Bootloader (U-Boot/ATF):完成复杂外设初始化、加载操作系统内核与设备树。
- 操作系统启动:内核接管系统,挂载根文件系统,启动用户空间。
关键概念: 复位向量是 CPU 上电后第一条指令的地址,通常位于 ROM 或 Flash 的起始位置(如 0x00000000 或 0xFFFF0000)。这是整个启动流程的物理起点。
2.2 复位向量与硬件初始化
CPU 上电后,硬件逻辑自动将程序计数器 (PC) 设置为复位向量地址。该地址通常指向 ROM 或 Flash 中的一段固定代码。此阶段完成以下操作:
- 设置栈指针 (SP):为后续 C 语言运行环境提供栈空间。
- 配置时钟与 PLL:将 CPU 和总线时钟从默认低频切换到目标频率。
- 初始化关键外设:如看门狗定时器、中断控制器、内存控制器。
- 跳转到 ROM 主函数:执行更复杂的初始化逻辑。
开发提示: 复位向量处的代码通常用汇编语言编写,因为此时 C 运行环境尚未建立。务必确保栈指针在跳转到 C 函数前已正确设置,否则会导致不可预知的崩溃。
2.3 ROM 代码的角色与职责
ROM 代码是固化在芯片内部 ROM 中的一段不可修改的启动代码,由芯片厂商提供。其主要职责包括:
| 功能模块 | 具体职责 | 典型实现 |
|---|---|---|
| 启动介质检测 | 根据 Boot Pin 或 eFuse 配置,选择从 NAND、eMMC、SD 卡、UART 或 USB 启动 | 读取 GPIO 电平状态 |
| 一级 Bootloader 加载 | 从选定的存储介质读取 SPL/MLO 到内部 SRAM | NAND 控制器、MMC 控制器驱动 |
| 安全校验 | 验证一级 Bootloader 的签名或哈希值 | SHA-256、RSA 验签 |
| 跳转执行 | 将控制权交给加载到 SRAM 中的一级 Bootloader | 修改 PC 寄存器 |
注意事项: ROM 代码不可修改,因此其支持的启动介质和协议是固定的。如果芯片 ROM 不支持某种存储介质(如 SPI NAND),则必须通过一级 Bootloader 的辅助代码来弥补。
2.4 一级 Bootloader (SPL/MLO)
一级 Bootloader 通常被称为 SPL (Secondary Program Loader) 或 MLO (Mmc Loader)。它由 ROM 代码加载到内部 SRAM 中执行,受限于 SRAM 大小(通常 64KB~256KB),其功能非常精简:
- 初始化 DDR 控制器:配置 DDR 时序、频率和地址映射,使外部 DDR 可用。
- 初始化存储控制器:为加载二级 Bootloader 准备存储访问能力。
- 加载二级 Bootloader:从存储介质(如 eMMC、NAND)读取 U-Boot 或 ATF 到 DDR。
- 可选安全启动:验证二级 Bootloader 的完整性。
- 跳转到 DDR:将控制权交给二级 Bootloader 的入口点。
// 一级 Bootloader 伪代码示例
void spl_main(void) {
// 1. 初始化 DDR
ddr_init(CONFIG_DDR_SPEED, CONFIG_DDR_TYPE);
// 2. 初始化存储设备
mmc_init(0); // 初始化 eMMC
// 3. 读取二级 Bootloader 到 DDR 地址 0x80000000
mmc_read(CONFIG_BL2_OFFSET, (void*)0x80000000, CONFIG_BL2_SIZE);
// 4. 可选安全校验
if (secure_boot_enabled()) {
verify_signature((void*)0x80000000, CONFIG_BL2_SIZE);
}
// 5. 跳转到二级 Bootloader
jump_to(0x80000000);
}
2.5 二级 Bootloader (U-Boot/ATF)
二级 Bootloader 是功能完整的引导加载程序,运行在 DDR 中,拥有更大的代码空间和更强的能力。常见的实现包括 U-Boot 和 ARM Trusted Firmware (ATF)。其核心职责如下:
| 功能 | 说明 | 典型命令/接口 |
|---|---|---|
| 外设初始化 | 初始化网络、USB、显示、文件系统等 | dm_init(); eth_init(); |
| 环境变量管理 | 存储启动参数、内核命令行、启动顺序 | printenv; setenv; saveenv |
| 内核加载 | 从存储或网络读取内核镜像到 DDR | fatload mmc 0:1 0x82000000 kernel.img |
| 设备树传递 | 将设备树二进制 (DTB) 加载并传递给内核 | fdt addr 0x83000000; bootz 0x82000000 - 0x83000000 |
| 启动内核 | 设置 CPU 寄存器,跳转到内核入口 | bootm; bootz; booti |
2.6 角色划分总结
下表清晰对比了 ROM 代码、一级 Bootloader 和二级 Bootloader 的核心差异:
| 特性 | ROM 代码 | 一级 Bootloader (SPL) | 二级 Bootloader (U-Boot) |
|---|---|---|---|
| 存储位置 | 芯片内部 ROM | Flash / SD 卡 / eMMC | Flash / SD 卡 / eMMC |
| 执行位置 | 内部 ROM | 内部 SRAM | 外部 DDR |
| 可修改性 | 不可修改(固化) | 可修改(需重新烧录) | 可修改(需重新烧录) |
| 大小限制 | 通常 4KB~64KB | 通常 64KB~256KB | 通常 256KB~4MB |
| 主要功能 | 启动介质检测、加载 SPL | DDR 初始化、加载 U-Boot | 外设初始化、加载内核 |
| 用户交互 | 无 | 通常无 | 命令行交互 |
| 安全启动 | 验签 SPL | 验签 U-Boot | 验签内核 |
2.7 启动流程中的关键时序
在实际开发中,启动时间是一个重要指标。以下是一个典型嵌入式系统的启动时序示例:
启动阶段 典型耗时
----------------------------------------
上电复位 + ROM 代码 5 ms ~ 20 ms
一级 Bootloader (SPL) 10 ms ~ 50 ms
二级 Bootloader (U-Boot) 100 ms ~ 500 ms
内核启动 + 根文件系统 500 ms ~ 2 s
----------------------------------------
总启动时间 615 ms ~ 2.57 s
优化建议: 如果系统对启动时间敏感,应优先优化二级 Bootloader 的初始化流程,例如裁剪不必要的驱动、使用快速存储接口、启用内核压缩等。一级 Bootloader 的优化空间有限,因其功能已非常精简。