2、启动流程概览:从CPU复位到内核启动的完整链路
各位同学,今天我们来聊聊U-Boot启动的完整链路。说实话,我刚入行那会儿,看着开发板上电后一串串日志飞过,心里是懵的。后来花了整整一周时间,才把这条链路理清楚。今天我把这些经验分享给你,希望能帮你少走弯路。
2.1 从CPU复位说起
CPU复位,是一切启动的起点。你按下开发板上的复位键,或者给板子重新上电,CPU会从某个固定的物理地址开始执行第一条指令。这个地址,通常叫做复位向量。
不同的CPU架构,复位向量不一样:
| 架构 | 复位向量地址 | 说明 |
|---|---|---|
| ARM | 0x00000000 或 0xFFFF0000 | 取决于具体实现,多数SoC用0x00000000 |
| RISC-V | 0x80000000 或 0x00001000 | 常见于QEMU和实际芯片 |
| x86 | 0xFFFFFFF0 | 靠近4GB地址空间顶部 |
我个人习惯,拿到一块新板子,第一件事就是查它的复位向量。为什么?因为这里放的是启动ROM代码,或者第一级bootloader。搞错了,后面全白搭。
2.2 启动的三大阶段
U-Boot的启动,说白了就是三段式接力跑。每一段都有明确的分工,跑完一段,把接力棒交给下一段。这三段分别是:
- SPL(Secondary Program Loader):第二级程序加载器
- TPL(Tertiary Program Loader):第三级程序加载器(可选)
- U-Boot proper:完整的U-Boot
你可能会问:为什么搞这么复杂?直接一个U-Boot不行吗?嗯,这里有个现实问题——芯片内部SRAM太小了。很多嵌入式芯片,内部SRAM只有几十KB,根本放不下完整的U-Boot。所以必须分阶段加载。
核心思想:SPL负责初始化DDR内存,TPL负责加载U-Boot proper到DDR中,U-Boot proper负责加载内核。
2.3 SPL:最小化的初始化
SPL是U-Boot的精简版。它只做三件事:
- 初始化最基本的硬件(时钟、串口、DDR控制器)
- 初始化存储设备(比如SD卡、NAND Flash、SPI Flash)
- 从存储设备中读取U-Boot proper,加载到DDR中,然后跳转过去
我在项目中遇到过一个问题:SPL加载U-Boot proper时,总是校验失败。查了两天,最后发现是DDR时序配置错了。SPL初始化DDR时,参数没设对,导致数据读出来全是错的。嗯,从那以后,我每次调DDR参数,都会先用内存测试工具跑一遍。
SPL的代码量很小,通常只有几十KB。它被放在芯片内部SRAM中运行,速度很快。但SRAM容量有限,所以SPL不能做太多事情。
2.4 TPL:中间过渡层
TPL不是必须的。它存在的意义,是解决一个尴尬的问题:SPL太大了,放不进SRAM。
你想想看,有些芯片的SRAM只有16KB。SPL要初始化DDR,要驱动存储设备,代码量很容易超过16KB。怎么办?再加一层!
TPL比SPL更小,它只做一件事:初始化DDR。然后TPL把SPL加载到DDR中运行,SPL再去加载U-Boot proper。
我的建议:如果你的芯片SRAM足够大(比如256KB以上),完全可以跳过TPL,直接用SPL加载U-Boot proper。少一层,少一个坑。
2.5 U-Boot proper:完整的引导加载器
U-Boot proper是完整的U-Boot。它运行在DDR中,有充足的内存空间。它的职责包括:
- 初始化所有外设(网卡、USB、显示等)
- 解析设备树(Device Tree)
- 执行环境变量中的启动命令
- 加载内核到内存中
- 跳转到内核入口点
U-Boot proper的启动日志,就是我们平时看到最多的那一大串。从"U-Boot 2023.04"开始,到"Starting kernel ..."结束。
2.6 完整启动流程SVG图
下面这张图,是我自己画的启动流程。你看一眼,就能把整个链路串起来。
2.7 各阶段的内存布局
搞清楚了流程,我们再看内存布局。每个阶段运行在哪个地址,心里要有数。
| 阶段 | 运行位置 | 典型大小 | 加载来源 |
|---|---|---|---|
| 启动ROM | 芯片内部ROM | 4KB - 32KB | 芯片出厂固化 |
| SPL | 内部SRAM | 16KB - 128KB | 从存储设备读取 |
| TPL | 内部SRAM | 4KB - 16KB | 从存储设备读取 |
| U-Boot proper | 外部DDR | 256KB - 2MB | 由SPL/TPL加载 |
| 内核 | 外部DDR | 几MB到几十MB | 由U-Boot加载 |
注意:我曾经遇到过一块板子,SPL编译出来刚好128KB,而SRAM只有128KB。看起来没问题,但运行时总是莫名其妙地崩溃。后来发现,SPL运行时需要栈空间,栈和代码挤在一起,栈溢出把代码覆盖了。所以,SPL大小一定要留出余量,至少留出4KB给栈。
2.8 启动日志中的关键信息
看启动日志,其实就是在看各个阶段的交接点。我总结了几条经验:
- 看到"U-Boot SPL":说明SPL已经开始运行了
- 看到"Trying to boot from MMC":说明SPL正在从SD卡读取U-Boot proper
- 看到"U-Boot 2023.04":说明U-Boot proper已经加载成功,开始运行
- 看到"Starting kernel ...":说明U-Boot已经完成使命,内核即将启动
如果日志在某个阶段卡住了,你就知道问题出在哪一段。比如,只看到SPL的日志,没看到U-Boot proper的日志,那问题大概率出在SPL加载U-Boot proper的过程中。
好了,这一章的内容就到这里。启动流程是U-Boot的骨架,搞清楚了它,后面分析日志、排查问题就有了方向。
一句话总结:CPU复位 → 启动ROM → SPL(初始化DDR)→ [可选TPL] → U-Boot proper(加载内核)→ 内核启动。每一步都有明确的职责,每一步都有对应的日志输出。