3. Bootloader与内核启动流程:从SoC上电到内核入口

嵌入式系统的启动是一个从硬件复位到操作系统运行的完整链条。理解Bootloader在其中的职责以及各阶段的划分,是掌握设备树加载与传递机制的前提。本章将从SoC上电开始,逐层拆解启动流程,明确Bootloader(以U-Boot为例)在每个阶段的核心工作。

3.1 启动流程全景:从复位向量到内核入口

典型的ARM/Linux嵌入式系统启动流程可划分为四个主要阶段。每个阶段都有明确的入口、执行环境和交付物。

核心概念: Bootloader是硬件与操作系统之间的桥梁,负责将内核从非易失存储介质加载到RAM,并传递必要的参数(包括DTB)给内核。
嵌入式Linux启动流程全景图 阶段1: ROM Boot 复位向量 → 固化ROM代码 初始化时钟、DDR 阶段2: SPL/MLO 加载U-Boot主程序 初始化存储控制器 阶段3: U-Boot 加载内核与DTB 设置启动参数 阶段4: Kernel 解压/初始化 挂载根文件系统 各阶段交付物与关键动作 • ROM Boot: 固化代码,初始化最小系统(时钟、DDR),加载SPL到SRAM • SPL/MLO: 初始化存储控制器,从Flash/MMC加载U-Boot主程序到DDR • U-Boot: 加载内核镜像(zImage/Image)与DTB到RAM,设置启动参数并跳转 • Kernel: 解压内核,解析DTB,初始化子系统,挂载根文件系统

3.2 Bootloader(U-Boot)的核心职责

U-Boot作为最主流的嵌入式Bootloader,在启动流程中承担以下关键职责。这些职责直接决定了设备树能否正确加载并传递给内核。

职责类别 具体工作 与设备树的关系
硬件初始化 初始化DDR控制器、串口、存储控制器(NAND/eMMC/SD)、网络接口 确保RAM可用,为加载DTB提供内存空间
设备探测与枚举 扫描总线(I2C/SPI/PCIe),识别外设型号与地址 动态修改DTB节点(如内存大小、MAC地址)
存储介质访问 从Flash、MMC、网络(TFTP)或USB读取内核与DTB DTB文件必须被正确寻址并加载到RAM
启动参数设置 构造bootargs环境变量,设置内核命令行 通过bootm或booti命令传递DTB地址
安全校验 验证内核与DTB的签名/哈希(如FIT镜像) DTB作为FIT镜像的一部分被校验
跳转到内核 设置CPU寄存器(r0/r1/r2),跳转到内核入口点 r2寄存器存放DTB物理地址(ARM惯例)
关键约定: 在ARM架构中,U-Boot跳转到内核时,约定r0=0,r1=机器类型ID(已废弃),r2=DTB在内存中的物理地址。这是内核解析设备树的唯一入口。

3.3 启动阶段详细划分与设备树加载时机

从SoC上电到内核入口,可以进一步细分为七个关键步骤。设备树(DTB)的加载与传递发生在U-Boot主阶段。

启动阶段详细划分与DTB加载时机 ① SoC上电复位 CPU从复位向量(0x00000000/0xFFFF0000)取指,执行固化ROM代码 ② ROM Boot加载SPL 从预设存储介质(SPI Flash/SD卡)加载SPL到内部SRAM ③ SPL初始化DDR 配置DDR控制器时序与参数,使能外部RAM空间 ④ SPL加载U-Boot主程序 从Flash/MMC读取U-Boot镜像到DDR指定地址(如0x80800000) ⑤ U-Boot加载内核与DTB 从存储介质读取zImage/Image到RAM,读取DTB到RAM(如0x88000000) ⑥ U-Boot设置启动参数 设置bootargs环境变量,配置r2寄存器指向DTB地址 ⑦ 跳转到内核入口 执行跳转指令,内核开始解压并解析DTB ROM 阶段 SPL 阶段 U-Boot 阶段 Kernel

3.4 U-Boot启动命令与DTB传递实例

在实际开发中,U-Boot通过环境变量和命令控制启动流程。以下是一个典型的启动命令序列,展示了DTB的加载与传递过程。

# 1. 从MMC分区加载内核镜像到RAM地址0x80800000
mmc read 0x80800000 0x1000 0x8000

# 2. 从MMC分区加载设备树到RAM地址0x88000000
mmc read 0x88000000 0x9000 0x1000

# 3. 设置内核命令行参数(bootargs)
setenv bootargs 'console=ttyS0,115200 root=/dev/mmcblk0p2 rw rootwait'

# 4. 启动内核,传递DTB地址
booti 0x80800000 - 0x88000000
常见错误: DTB加载地址与内核解压地址重叠会导致内核覆盖DTB,造成解析失败。建议将DTB加载到内核地址之后的高地址区域,并确保两者之间有足够的间隔(通常至少2MB)。

3.5 启动阶段对比:不同SoC的差异

不同厂商的SoC在启动阶段划分上存在差异,主要体现在ROM Boot和SPL的实现方式上。下表对比了三种主流架构的启动特点。

特性 ARM (Cortex-A系列) RISC-V x86 (UEFI)
复位向量 0x00000000 或 0xFFFF0000 0x80000000 (典型值) 0xFFFFFFF0 (传统BIOS)
ROM Boot 固化BootROM,支持多种启动介质 通常由片上BootROM实现 BIOS/UEFI固件
SPL/第一阶段 SPL (Secondary Program Loader) ZSBL (Zeroth Stage Boot Loader) UEFI SEC/PEI阶段
主Bootloader U-Boot (支持FIT镜像) U-Boot / OpenSBI GRUB / UEFI Shell
DTB传递方式 r2寄存器传递物理地址 a1寄存器传递物理地址 UEFI配置表传递
典型加载地址 内核: 0x80008000, DTB: 0x88000000 内核: 0x80200000, DTB: 0x88000000 由UEFI分配

3.6 启动流程中的关键检查点

在调试启动问题时,以下检查点可以帮助快速定位Bootloader或DTB传递阶段的故障。

  • 串口输出检查: U-Boot启动时是否打印版本信息?如果无输出,说明ROM Boot或SPL阶段失败。
  • 内存映射验证: 使用md命令检查DTB加载地址是否包含正确的魔数(0xD00DFEED)。
  • 环境变量确认: printenv查看bootargs、fdt_addr_r、kernel_addr_r是否正确。
  • FDT校验: 使用fdt addr命令加载DTB后,执行fdt list查看节点是否可解析。
  • 启动命令追踪: 设置bootdelay=3并中断启动,手动执行booti或bootm观察错误信息。
调试技巧: 在U-Boot中执行 fdt addr 0x88000000 && fdt list / 可以快速验证DTB是否已正确加载到指定地址且格式有效。如果返回"FDT_ERR_BADMAGIC",说明地址错误或DTB损坏。

3.7 本章小结