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的精简版。它只做三件事:

  1. 初始化最基本的硬件(时钟、串口、DDR控制器)
  2. 初始化存储设备(比如SD卡、NAND Flash、SPI Flash)
  3. 从存储设备中读取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图

下面这张图,是我自己画的启动流程。你看一眼,就能把整个链路串起来。

U-Boot启动完整链路 CPU复位 启动ROM(固化代码) SPL(初始化DDR) TPL(可选) U-Boot proper 加载内核到DDR 内核启动 各阶段存储位置 • 启动ROM:芯片内部ROM • SPL:内部SRAM • TPL:内部SRAM • U-Boot proper:外部DDR • 内核:外部DDR ⚠️ 常见坑点 • DDR时序配置错误 • SPL大小超过SRAM • 存储设备驱动未初始化

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(加载内核)→ 内核启动。每一步都有明确的职责,每一步都有对应的日志输出。

可可精品资料库