2. 嵌入式系统启动流程

嵌入式系统的启动不是“一上电就跑 Linux”这么简单,而是一条由硬件复位信号触发、逐级接力、层层验证的链式过程。每一级代码都承担着“初始化自身运行环境、找到并加载下一级镜像、把控制权交出去”的职责。理解这条链路,是设计 Bootloader 架构的前提:只有知道每一级在什么内存中运行、能访问哪些外设、失败时如何兜底,才能合理划分 SPL 与 Bootloader 的功能边界。

一、核心原理:为什么启动要分这么多级

根本原因在于“启动时的可用资源”与“最终系统的需求”之间存在巨大落差。上电瞬间,SoC 内部只有极小的 SRAM(通常几十 KB 到几百 KB),DDR 尚未初始化,外部存储(eMMC、NAND、SPI NOR)的控制器也未配置。而完整的操作系统镜像可能有几十 MB,必须放在 DDR 中运行。因此启动过程必须被拆成若干阶段,让每一级用“当前可用的最小资源”去换取“下一级所需的更大资源”。

这条链路通常包含五个关键节点:

  • 上电复位:电源稳定后复位释放,CPU 从固定复位向量取指,此时只有片内 ROM 和 SRAM 可用。
  • ROM Code:固化在 SoC 内的只读代码,负责最基础的时钟、引脚复用配置,并按预设顺序扫描启动介质,加载下一级镜像。
  • SPL:二级程序加载器,体积小,核心任务是初始化 DDR 控制器和时钟树,把完整的 Bootloader 搬进 DDR。
  • Bootloader:如 U-Boot,运行在 DDR 中,负责加载内核镜像、设备树和根文件系统,并传递启动参数。
  • 内核与根文件系统:内核解压、初始化驱动、挂载根文件系统,最终启动用户空间的 init 进程。

二、完整启动链路图解

下图展示了从复位到用户空间的信号与数据流:每一级镜像的来源、运行位置以及控制权交接点都标注清楚,便于对照实际芯片手册理解。

嵌入式系统五级启动链路 ① 上电复位 Power-On Reset 复位向量取指 ② ROM Code 片内只读固件 扫描启动介质 ③ SPL 二级程序加载器 初始化 DDR ④ Bootloader U-Boot 等 加载内核与 DTB ⑤ 内核 + 根文件系统 启动 init 运行位置与镜像来源 片内 ROM 复位向量 片内 SRAM ROM Code 运行 SRAM → DDR SPL 搬运 DDR Bootloader 运行 DDR 内核运行 启动介质扫描顺序(典型) SPI NOR eMMC / SD NAND Flash USB / UART 网络 TFTP 控制权交接点:ROM Code → SPL → Bootloader → Kernel 关键约束:每一级只能使用当前已初始化的资源;DDR 初始化是 SPL 存在的根本理由

三、方法流程:每一级到底做了什么

上电复位阶段:电源管理芯片输出复位释放信号后,CPU 从芯片手册规定的复位向量地址取第一条指令。此时 PLL 未锁定,CPU 通常以低频晶振或内部 RC 时钟运行,Cache 和 MMU 均关闭,只能访问片内 ROM 和 SRAM。

ROM Code 阶段:这段代码由芯片厂商固化,不可修改。它完成最小化的时钟与引脚配置,然后按“启动介质优先级”依次探测:若 SPI NOR 前几个字节有合法头部则从该介质加载;否则切换到 eMMC、SD、NAND,最后可能进入 USB/UART 下载模式。加载目标通常是片内 SRAM,加载完成后跳转到 SPL 入口。

SPL 阶段:SPL 是整条链路中技术含量最高的一环。它必须在极小的体积内完成 DDR 控制器的寄存器配置、DDR 训练(写电平、读门控、VREF 训练)、时钟树切换,然后把完整 Bootloader 从启动介质搬运到 DDR 并跳转。很多平台还会在 SPL 中做安全启动校验。

Bootloader 阶段:以 U-Boot 为例,它运行在 DDR 中,拥有完整的外设驱动。它读取环境变量,决定是从存储、网络还是 USB 加载内核;把内核镜像、设备树和 initramfs 放到 DDR 指定地址;设置 bootargs 后跳转到内核入口。

内核与根文件系统阶段:内核自解压后初始化内存管理、中断控制器、驱动模型,根据设备树匹配并加载驱动,最后挂载根文件系统并执行 init。至此启动链路完成,系统进入用户空间。

四、典型场景与启动日志

以一颗 ARM Cortex-A 芯片从 eMMC 启动为例,串口会依次输出各级日志。观察日志的分界点,可以快速判断启动卡在哪一级:

ROM: BOOTROM v2.1
Boot media: eMMC
Load SPL from offset 0x100 ... OK
Jump to SPL @ 0x20000

U-Boot SPL 2023.07
DDR: 2GB, 2400MT/s, training done
Loading U-Boot from eMMC ... OK
Jump to U-Boot @ 0x4a000000

U-Boot 2023.07
Hit any key to stop autoboot: 0
Loading kernel Image from mmc 0:1 ... OK
Loading fdt from mmc 0:1 ... OK
Booting kernel from DDR 0x40080000

[    0.000000] Booting Linux on physical CPU 0x0
[    0.000000] Linux version 6.1.0
[    2.134521] Run /sbin/init as init process

如果日志停在 “Load SPL” 之后没有 SPL 输出,说明 ROM Code 加载成功但 SPL 入口或 DDR 初始化失败;如果停在 “Loading U-Boot” 之后,则问题多在 DDR 训练或镜像偏移地址配置。

五、常见误区与复盘要点

误区一:把 SPL 当成“小 U-Boot”。SPL 的职责边界非常窄——初始化 DDR 并搬运下一级,不应在其中加入网络、文件系统等复杂功能,否则体积膨胀会超出 SRAM 容量。

误区二:忽略镜像偏移地址。ROM Code 按固定偏移读取 SPL,SPL 按配置偏移读取 U-Boot。分区表或烧录脚本中偏移写错,表现为“烧录成功但无法启动”。

误区三:DDR 训练参数未固化。部分平台每次上电都重新训练,若训练不稳定会导致偶发启动失败,量产时应把训练结果写入 OTP 或环境变量。

启动阶段 运行位置 核心任务 典型失败现象
上电复位 片内 ROM 取复位向量,释放复位域 无任何串口输出
ROM Code 片内 SRAM 扫描介质,加载 SPL 提示无有效启动介质
SPL SRAM → DDR DDR 初始化与训练,搬运 U-Boot 卡在 DDR training
Bootloader DDR 加载内核、DTB、根文件系统 autoboot 后无内核日志
内核与根文件系统 DDR 驱动初始化,挂载 rootfs,启动 init Kernel panic: no init found

复盘启动问题时,建议按“串口日志最后一行 → 对应阶段 → 该阶段依赖的硬件资源”三步定位。绝大多数启动失败集中在 DDR 训练、镜像偏移和根文件系统挂载参数三处,而非内核本身。