3. 嵌入式启动流程概览
嵌入式系统的启动并不是“一按电源就进 Linux”这么简单。以 RISC-V 平台为例,从上电到用户态 shell 出现,中间要经过多个阶段,每个阶段运行在不同的存储介质、不同的特权级、不同的地址空间上,并且逐级把控制权“接力”给下一阶段。理解这条完整链路,是后续调试 U-Boot、分析内核启动卡死、定位设备树问题的前提。
本节把整条链路拆成六个关键阶段:上电复位 → BootROM → SPL/一级引导 → U-Boot 主程序 → Linux 内核 → 用户空间 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。启动过程本质上也是特权级逐级“降权”的过程。
二、方法流程:六个阶段各自做什么
- 上电复位:CPU 从固定复位向量取指,通常指向片内 BootROM 的入口。此时只有时钟和极少量 SRAM 可用。
- BootROM:根据启动引脚(如 SPI Flash、SD、UART)选择启动介质,读取头部信息,校验并加载 SPL 到片内 SRAM。
- SPL / 一级引导:初始化 DDR 控制器、时钟和基本串口,然后把 U-Boot 主程序从 Flash 搬到 DDR 并跳转。
- U-Boot 主程序:提供命令行、环境变量、网络和存储驱动,加载 Linux 内核镜像(Image)和设备树(DTB),准备启动参数。
- Linux 内核:解压自身、建立页表、初始化内存管理、驱动模型,解析 DTB,挂载根文件系统。
- 用户空间 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 编译、设备树传递、内核命令行参数时,就能清楚知道每个配置项作用在哪个阶段、影响哪一级的启动行为。