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 进程。
二、完整启动链路图解
下图展示了从复位到用户空间的信号与数据流:每一级镜像的来源、运行位置以及控制权交接点都标注清楚,便于对照实际芯片手册理解。
三、方法流程:每一级到底做了什么
上电复位阶段:电源管理芯片输出复位释放信号后,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 训练、镜像偏移和根文件系统挂载参数三处,而非内核本身。