3. 嵌入式启动流程概览

嵌入式系统的启动并不是“一按电源就进 Linux”这么简单。以 RISC-V 平台为例,从上电到用户态 shell 出现,中间要经过多个阶段,每个阶段运行在不同的存储介质、不同的特权级、不同的地址空间上,并且逐级把控制权“接力”给下一阶段。理解这条完整链路,是后续调试 U-Boot、分析内核启动卡死、定位设备树问题的前提。

本节把整条链路拆成六个关键阶段:上电复位 → BootROM → SPL/一级引导 → U-Boot 主程序 → Linux 内核 → 用户空间 init。每一级只做自己该做的事,然后把“接力棒”交给下一级。

RISC-V 嵌入式启动链路:从上电到 Linux 用户空间 上电复位 PC=复位向量 BootROM 固化代码 / 只读 SPL / 一级引导 初始化 DDR U-Boot 主程序 加载内核镜像 Linux 内核 解压 / 初始化 init 用户态 特权级 / 运行环境 M-mode M-mode M-mode S-mode S-mode U-mode 代码来源 芯片内部 ROM BootROM 内 SPI Flash / SD DDR / Flash DDR 根文件系统 关键交接动作 取复位向量 校验并加载 SPL 初始化 DDR,搬 U-Boot 加载内核 + DTB 跳转内核入口 执行 /sbin/init 每一级只负责“初始化必要硬件 + 把下一级镜像搬到正确位置 + 跳转”,形成逐级接力的启动链 调试思路:先确认卡在哪一级,再检查该级的镜像、地址、参数是否正确

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

根本原因有两个:存储介质的速度差异和硬件初始化的先后依赖。芯片刚上电时,DDR 还没有初始化,CPU 只能从片内 ROM 或很小的 SRAM 取指执行。BootROM 的容量通常只有几十 KB,放不下完整的 U-Boot,更放不下 Linux 内核。因此必须先用一小段代码把 DDR 训练好,再把更大的镜像搬进 DDR,才能继续往下走。

另一个原因是特权级切换。RISC-V 定义了 M-mode(机器模式)、S-mode(监管模式)、U-mode(用户模式)。BootROM 和 SPL 通常运行在 M-mode,负责最底层的时钟、DDR、引脚复用;U-Boot 主程序和 Linux 内核运行在 S-mode;用户程序运行在 U-mode。启动过程本质上也是特权级逐级“降权”的过程。

启动链路的本质:用最小可信代码,逐级验证并加载更大、更复杂的镜像,最终把控制权交给操作系统。

二、方法流程:六个阶段各自做什么

  1. 上电复位:CPU 从固定复位向量取指,通常指向片内 BootROM 的入口。此时只有时钟和极少量 SRAM 可用。
  2. BootROM:根据启动引脚(如 SPI Flash、SD、UART)选择启动介质,读取头部信息,校验并加载 SPL 到片内 SRAM。
  3. SPL / 一级引导:初始化 DDR 控制器、时钟和基本串口,然后把 U-Boot 主程序从 Flash 搬到 DDR 并跳转。
  4. U-Boot 主程序:提供命令行、环境变量、网络和存储驱动,加载 Linux 内核镜像(Image)和设备树(DTB),准备启动参数。
  5. Linux 内核:解压自身、建立页表、初始化内存管理、驱动模型,解析 DTB,挂载根文件系统。
  6. 用户空间 init:内核启动第一个用户进程(如 /sbin/init 或 systemd),完成服务启动,最终进入 shell。

三、案例场景:一次典型的 RISC-V 启动日志

下面是一段简化的串口启动日志,可以看到各阶段的“接力”痕迹:

BootROM: v1.0
Loading SPL from SPI Flash ... OK
SPL: DDR init done, size=512MB
SPL: loading U-Boot to 0x80200000 ... OK
U-Boot 2024.01 (RISC-V)
DRAM:  512 MiB
Loading kernel from mmc 0:1 ...
Loading DTB from mmc 0:1 ...
Booting Linux on hart 0 ...
[    0.000000] Linux version 6.6.0
[    0.000000] Kernel command line: console=ttyS0 root=/dev/mmcblk0p2
[    2.345678] Run /sbin/init as init process

如果启动卡在 Loading SPL,问题通常在 BootROM 的启动介质选择或 Flash 头部;卡在 DDR init,多半是 DDR 参数或电源时序;卡在 Loading kernel,则要检查 U-Boot 的 bootcmd、分区表和镜像地址。

四、关键组件与职责对照

阶段 运行位置 核心职责 常见故障点
BootROM 片内 ROM 选择启动介质、加载 SPL 启动引脚配置错误
SPL 片内 SRAM 初始化 DDR、搬 U-Boot DDR 参数不匹配
U-Boot DDR 加载内核与 DTB、传参 bootcmd / 分区错误
Linux 内核 DDR 驱动初始化、挂载根文件系统 DTB 不匹配、root= 错误
init DDR 启动用户态服务 根文件系统缺失
调试启动问题时,最有效的方法是先定位卡在哪一级:看串口最后一行输出属于哪个阶段,再针对该阶段的镜像、地址、参数逐一排查,而不是盲目重烧整个系统。
常见误区:把 U-Boot 和 SPL 混为一谈。SPL 是 U-Boot 工程编译出的“精简版”,只做 DDR 初始化和搬运;U-Boot 主程序才提供命令行和加载内核的能力。两者镜像不同、加载地址不同,烧录时不能互换。

把这条链路记牢,后续学习 U-Boot 编译、设备树传递、内核命令行参数时,就能清楚知道每个配置项作用在哪个阶段、影响哪一级的启动行为。