2. Bootloader基础:Bootloader的启动阶段(ROM Code → SPL → U-Boot)
嵌入式系统的启动并非一蹴而就,而是一个由硬件固化逻辑逐步引导至复杂软件环境的多阶段接力过程。以主流ARM SoC(如TI OMAP、Allwinner、NXP i.MX系列)为例,典型的Bootloader启动路径分为三个阶段:ROM Code → SPL(Secondary Program Loader) → U-Boot proper。理解每一阶段的职责、约束与交接点,是优化启动时序的前提。
核心观点: 启动时序优化的本质,是压缩三个阶段之间的“加载间隙”——即从ROM Code完成硬件初始化到SPL加载U-Boot,再到U-Boot加载内核之间的等待与传输时间。
2.1 三阶段启动架构总览
下表对比了三个阶段在存储位置、执行环境、主要职责和典型大小上的差异:
| 阶段 | 存储位置 | 执行环境 | 主要职责 | 典型大小 |
|---|---|---|---|---|
| ROM Code | 芯片内部ROM(掩膜) | SRAM(片内紧耦合RAM) | 初始化时钟、PLL、DDR控制器;从启动介质读取SPL | 4KB ~ 32KB |
| SPL | 启动介质(NAND/eMMC/SD卡) | SRAM 或 DDR(早期初始化后) | 初始化DDR、存储控制器;加载U-Boot proper到DDR | 16KB ~ 64KB |
| U-Boot proper | 启动介质 | DDR(主存) | 完整外设驱动、环境变量、文件系统支持;加载并启动内核 | 200KB ~ 1MB+ |
优化提示: ROM Code 通常由芯片厂商固化,无法修改。启动时序优化的主要发力点集中在 SPL 和 U-Boot proper 阶段,尤其是 SPL 对 DDR 的初始化速度以及 U-Boot 对存储介质的读取效率。
2.2 阶段一:ROM Code(一级引导)
ROM Code 是芯片上电后CPU执行的第一段代码,固化在芯片内部的只读存储器中。其执行环境极为受限:
- 存储限制: 代码大小受片内SRAM容量约束(通常4KB~32KB)。
- 功能限制: 仅包含最基础的硬件初始化——设置系统时钟、PLL锁相环、初始化片内SRAM、配置启动介质控制器。
- 启动介质检测: 按照预定义的启动顺序(如SD卡→eMMC→NAND Flash→UART)轮询各介质,读取固定偏移处的SPL镜像。
ROM Code 的典型执行流程:
- 上电复位,CPU从0x0000_0000(或异常向量表)开始执行。
- 初始化CPU核心模式、堆栈指针(SP)。
- 配置基本时钟源(如外部晶振→PLL→系统时钟)。
- 初始化片内SRAM(通常作为临时运行区)。
- 根据启动引脚电平或eFuse配置,选择启动介质。
- 从介质头部读取SPL镜像(通常为1KB~64KB)到SRAM。
- 验证SPL的完整性(如CRC或签名校验),跳转到SPL入口。
时序瓶颈: ROM Code 阶段的主要耗时在于PLL锁定等待(微秒级)和从慢速介质(如NAND)读取SPL头部。若启动介质为低速SPI NOR Flash,读取时间可能达到毫秒级,是早期优化的关键点。
2.3 阶段二:SPL(二级引导 / MLO)
SPL(Secondary Program Loader)是ROM Code加载到SRAM中的第一段可编程代码。在U-Boot体系中,SPL通常由U-Boot源码编译生成(如 u-boot-spl.bin),其大小被严格限制在SRAM容量内。SPL的核心使命是“初始化DDR,然后加载U-Boot proper”。
SPL 的典型执行步骤:
- 精简硬件初始化: 再次确认时钟树、初始化DDR控制器(DRAM)、配置存储控制器(如MMC/SD/NAND接口)。
- 设置栈与BSS: 在SRAM中建立C语言运行环境。
- 读取U-Boot proper: 从启动介质中读取U-Boot主镜像到DDR的固定地址(如0x8000_0000)。
- 可选的校验: 验证U-Boot镜像的完整性(CRC32/SHA256)。
- 跳转: 修改PC指针到DDR中的U-Boot入口地址。
时序关键点: SPL阶段的最大耗时操作是DDR初始化(DDR PHY训练、ZQ校准,通常耗时1~10ms)和从存储介质读取U-Boot镜像(取决于介质速度,eMMC约10~50ms,NAND约50~200ms)。优化SPL的读取策略(如DMA传输、缓存预取)能显著缩短加载间隙。
2.4 阶段三:U-Boot proper(主引导加载程序)
U-Boot proper 是功能完整的Bootloader,运行在DDR中,拥有充足的代码空间和内存资源。它负责:
- 完整外设驱动: 网卡、USB、显示、文件系统(FAT/ext4)等。
- 环境变量管理: 存储启动参数(bootargs)、启动命令(bootcmd)。
- 内核加载与启动: 从网络(TFTP)、存储介质(eMMC分区)或USB读取内核镜像(zImage/Image)、设备树(DTB)和ramdisk。
- 提供交互式Shell: 允许开发者调试、修改启动参数。
U-Boot proper 的启动流程(简化):
- 从SPL跳转后,初始化剩余外设(如网卡、USB控制器)。
- 读取并解析环境变量(通常存储在eMMC的特定分区或SPI Flash)。
- 执行
bootcmd中的命令序列(如load mmc 0:1 ${loadaddr} zImage)。 - 加载内核镜像和设备树到DDR指定地址。
- 设置内核启动参数(ATAGS或设备树传递)。
- 调用
bootz或bootm命令,跳转到内核入口。
时序陷阱: U-Boot proper 阶段容易引入不必要的延迟,例如:
- 等待网络自动协商超时(若未配置静态IP)。
- 文件系统驱动初始化耗时(如ext4日志回放)。
- 环境变量存储介质读取慢(如SPI Flash)。
优化策略包括:裁剪不需要的驱动、使用
- 等待网络自动协商超时(若未配置静态IP)。
- 文件系统驱动初始化耗时(如ext4日志回放)。
- 环境变量存储介质读取慢(如SPI Flash)。
优化策略包括:裁剪不需要的驱动、使用
bootdelay=0 跳过倒计时、将环境变量存储在快速介质中。
2.5 三阶段加载间隙的时序分解
将三个阶段串联起来,可以清晰地看到“加载间隙”的分布:
| 阶段 | 典型耗时 | 加载间隙描述 | 优化潜力 |
|---|---|---|---|
| ROM Code | 0.5 ~ 5 ms | 从复位到SPL就绪 | 低(固化代码) |
| SPL → DDR初始化 | 1 ~ 10 ms | DDR PHY训练、时序校准 | 中(调整训练参数) |
| SPL → 读取U-Boot | 10 ~ 200 ms | 从介质读取U-Boot镜像到DDR | 高(DMA、介质选择) |
| U-Boot proper → 内核 | 50 ~ 500 ms | 外设初始化、文件系统、内核加载 | 高(裁剪、并行化) |
优化目标: 将SPL + U-Boot proper 的总耗时从典型的 200~700ms 压缩到 100ms 以内,为实时性要求高的系统(如汽车电子、工业控制)争取宝贵的启动时间。
2.6 实践:查看当前系统的启动阶段耗时
在U-Boot命令行中,可以通过以下方法测量各阶段的耗时:
# 在U-Boot中启用时间戳
setenv bootdelay 0
setenv bootcmd 'mmc read ${loadaddr} 0x800 0x4000; bootz ${loadaddr}'
# 使用 bootstage 命令(需配置 CONFIG_BOOTSTAGE)
bootstage report
# 输出示例:
# Timer summary in microseconds:
# 0 start
# 1234 spl_start
# 45678 uboot_start
# 123456 kernel_start
调试建议: 在SPL和U-Boot的入口/出口处添加GPIO翻转,用示波器或逻辑分析仪测量实际电平变化,可以获得比软件时间戳更精确的时序数据。这是启动时序优化中最可靠的测量手段。