1. U-Boot启动流程概述:从SoC复位到BootROM,再到SPL和U-Boot proper的加载与执行
嵌入式Linux系统的启动是一个多阶段、高度分层的硬件-软件协同过程。理解从SoC复位到U-Boot proper执行完毕的完整链路,是进行启动优化的前提。本章将拆解这一流程中的关键阶段、各阶段的职责以及数据流传递方式。
1.1 启动流程全景图
下图展示了从SoC上电复位到U-Boot proper加载Linux内核的完整阶段划分。每个阶段都承担着特定的硬件初始化和加载任务,且前一阶段的输出是后一阶段的输入。
1.2 各阶段详细职责与数据流
下表总结了启动流程中每个核心阶段的职责、典型执行时间窗口以及输出产物。理解这些差异有助于定位启动瓶颈。
| 阶段 | 存储位置 | 主要职责 | 典型耗时 | 输出/跳转目标 |
|---|---|---|---|---|
| SoC复位 | 硬件逻辑 | 复位向量跳转、PLL/时钟初始化、基础IO配置 | 1-5 ms | BootROM入口地址 |
| BootROM | 芯片内部ROM | 启动介质检测、安全校验、加载SPL到SRAM | 5-50 ms | SPL二进制(SRAM中) |
| SPL | SRAM(片上RAM) | DRAM初始化、基础外设时钟、加载U-Boot proper | 10-100 ms | U-Boot proper(DRAM中) |
| U-Boot proper | DRAM | 完整外设驱动、环境变量处理、内核加载与引导 | 100-500 ms | Linux内核(DRAM中) |
1.3 SoC复位阶段详解
SoC上电或硬件复位后,CPU核心立即从预定义的复位向量(Reset Vector)开始执行。对于ARM架构,复位向量通常位于0x00000000或0xFFFF0000;对于RISC-V,则通常为0x80000000或芯片自定义地址。
复位阶段的核心动作包括:
- 硬件状态复位:所有寄存器恢复默认值,Cache/TLB失效,MMU关闭。
- 最小系统时钟初始化:PLL锁定,CPU核心时钟、总线时钟、外设时钟开始运行。
- 启动介质采样:硬件逻辑根据BOOT引脚电平或eFuse配置,确定第一启动介质(如SD卡、eMMC、SPI NOR)。
- 跳转到BootROM:硬件将PC指针指向芯片内部BootROM的起始地址。
1.4 BootROM阶段详解
BootROM是固化在芯片内部ROM中的一段不可修改的代码。它是SoC复位后执行的第一段软件,负责将二级引导程序(SPL)从存储介质加载到片上SRAM中。
BootROM的典型执行流程如下:
- 启动介质枚举:按照预定义优先级(如SD卡→eMMC→SPI NOR→NAND)依次检测介质是否存在且可读。
- 读取头部信息:从介质固定偏移(如LBA 0或偏移0x400)读取SPL镜像的头部,获取镜像大小、加载地址、校验和等信息。
- 镜像加载与校验:将SPL镜像从介质复制到SRAM指定地址,并进行CRC或签名校验(若使能安全启动)。
- 跳转到SPL:将PC指针设置为SPL入口地址,开始执行SPL代码。
1.5 SPL阶段详解
SPL(Secondary Program Loader)是U-Boot的轻量级前身,运行在容量有限的SRAM中。它的核心使命是:初始化DRAM,然后将U-Boot proper从存储介质加载到DRAM中。
SPL的典型代码结构如下:
// SPL入口函数(以ARM为例)
void board_init_f(ulong boot_flags)
{
// 1. 初始化时钟和基础外设
clock_init();
timer_init();
// 2. 初始化串口(用于调试输出)
preloader_console_init();
// 3. 初始化DRAM控制器
dram_init();
// 4. 获取DRAM大小
ulong dram_size = get_ram_size();
// 5. 从存储介质加载U-Boot proper到DRAM
spl_load_image(CONFIG_SYS_UBOOT_BASE);
// 6. 跳转到U-Boot proper入口
spl_jump_to_image_linux();
}
SPL阶段的关键优化点包括:
- DRAM初始化时序:使用更激进的时序参数(如降低CAS延迟)可减少初始化等待时间。
- 存储介质读取速度:配置SPL使用更高的总线时钟(如SDIO时钟从25MHz提升到50MHz)。
- 镜像加载粒度:使用更大的读取块大小(如从512字节提升到4096字节)减少读取次数。
1.6 U-Boot proper阶段详解
U-Boot proper是功能完整的引导加载程序,运行在DRAM中。它负责完成所有外设初始化、环境变量处理、设备树加载,并最终引导Linux内核。
U-Boot proper的启动流程可分解为以下步骤:
- board_init_f:早期初始化,设置堆栈、全局数据结构(GD)、串口、定时器。
- relocate_code:将U-Boot自身从加载地址重定位到DRAM高地址区域,为内核腾出空间。
- board_init_r:后期初始化,包括环境变量加载、设备树处理、存储驱动初始化。
- main_loop:进入命令行循环,等待用户输入或自动执行bootcmd启动内核。
- bootm/booti:解析内核镜像格式,设置启动参数,跳转到内核入口。
1.7 启动流程中的关键数据传递
在整个启动链路中,各阶段之间通过特定的数据结构传递信息。理解这些数据流有助于在优化时定位信息传递瓶颈。
| 传递方向 | 数据结构 | 存储位置 | 关键内容 |
|---|---|---|---|
| BootROM → SPL | 启动参数块(Boot Parameter Block) | SRAM固定地址 | 启动介质类型、时钟频率、调试标志 |
| SPL → U-Boot proper | 全局数据结构(GD) | DRAM低地址 | DRAM大小、栈指针、控制台标志 |
| U-Boot proper → Linux内核 | 设备树(DTB)+ ATAG/EFI Stub | DRAM指定地址 | 硬件拓扑、内存布局、内核命令行 |
dram_init()返回正确的内存大小是启动稳定性的基础。
1.8 本章小结
从SoC复位到U-Boot proper执行完毕,嵌入式Linux的启动是一个环环相扣的多阶段过程:
- SoC复位由硬件完成,不可编程,但决定了启动介质的选择。
- BootROM固化在芯片内部,负责加载SPL,无法修改但可以优化介质布局。
- SPL是优化的第一战场,DRAM初始化和介质读取速度是核心优化点。
- U-Boot proper承担了最重的初始化工作,环境变量处理、设备树加载和内核引导是耗时大户。