1. 课程导入

大家好,我是你们这门课的老朋友。做嵌入式这么多年,我见过太多工程师在启动流程上栽跟头。说白了,搞懂从按下电源到系统跑起来这整个过程,是咱们移植 U-Boot 和调试启动问题的基本功。今天这第一讲,咱们就把这条启动链从头到尾捋一遍。

1.1 按下电源键之后,芯片在干嘛?

你按下电源键,芯片上电。这时候内存还没初始化,CPU 也处于最原始的状态。芯片内部固化了一段只读代码,叫 BootROM。这段代码是芯片出厂就写死的,你改不了它。

BootROM 的任务很简单:

  • 初始化最基本的硬件(比如时钟、片内 SRAM)
  • 从预定义的启动介质读取下一级代码
  • 把代码加载到片内 SRAM 并跳转执行

我当年调试一块新板子,死活起不来。查了三天,最后发现是 BootROM 的启动介质选择引脚电平没拉对。嗯,这种坑踩过一次就记住了。

我的习惯:拿到新芯片,第一件事就是看芯片手册里 BootROM 那一章。搞清楚它支持哪些启动介质,引脚怎么配置。这能省下后面大量的调试时间。

1.2 SPL:第一级引导加载程序

BootROM 加载的下一级代码,通常叫 SPL(Secondary Program Loader)。为什么需要 SPL?因为 U-Boot 本体太大了,片内 SRAM 装不下。SPL 就是个小号的加载器,负责初始化 DDR 内存,然后把完整的 U-Boot 从存储介质搬到内存里。

SPL 的特点:

  • 体积小,通常几十 KB
  • 运行在片内 SRAM 中
  • 只做必要的初始化:时钟、DDR、存储控制器
  • 加载 U-Boot 到 DDR 后跳转

我记得有一次移植新平台,SPL 加载 U-Boot 时总报校验错误。查到最后,是 DDR 时序参数配错了,导致数据读出来是乱的。所以我说,DDR 初始化是 SPL 里最关键的环节,没有之一。

1.3 U-Boot:真正的引导加载程序

U-Boot 被 SPL 加载到 DDR 后,就接管了控制权。它比 SPL 复杂得多,功能也丰富得多。U-Boot 主要做这几件事:

  1. 完整硬件初始化:串口、网卡、存储设备、USB 等
  2. 加载内核:从 Flash、SD 卡、网络等介质读取内核镜像
  3. 传递启动参数:通过设备树(DTB)和内核命令行参数告诉内核硬件信息
  4. 跳转到内核:准备好环境后,把控制权交给内核

关键点:U-Boot 和内核之间的接口是设备树(Device Tree)。设备树描述了硬件信息,U-Boot 把它加载到内存,把地址告诉内核。如果设备树不对,内核要么认不出硬件,要么直接崩溃。

1.4 内核启动与根文件系统挂载

U-Boot 跳转到内核后,内核开始执行。内核会:

  • 解压自身(如果是压缩镜像)
  • 初始化内存管理、进程调度、中断系统
  • 枚举并初始化所有硬件设备
  • 挂载根文件系统
  • 启动第一个用户空间进程(通常是 init)

根文件系统是操作系统运行的基础。它包含了所有用户空间的程序、库、配置文件。没有根文件系统,内核启动到一半就会 panic,因为找不到 init 进程。

你想想看,整个启动链就像接力赛:BootROM 传给 SPL,SPL 传给 U-Boot,U-Boot 传给内核,内核挂载根文件系统。每一棒都不能掉。

1.5 完整启动链全景图

下面这张图把整个流程串起来了。我建议你把它存下来,后面调试的时候对照着看。

嵌入式系统完整启动链 嵌入式系统完整启动链 按下电源 → 芯片上电 BootROM 初始化时钟、SRAM,加载SPL SPL(第一级引导) 初始化DDR,加载U-Boot到内存 U-Boot(第二级引导) 加载内核和设备树,传递参数 Linux 内核 初始化硬件,挂载根文件系统 根文件系统 启动 init 进程,进入用户空间 各阶段关键点 • BootROM:芯片固化,不可修改 • SPL:体积小,运行在SRAM • U-Boot:功能丰富,交互式 • 内核:硬件抽象与资源管理 • 根文件系统:用户空间基础 常见问题: 1. DDR时序错误→SPL加载失败 2. 设备树不匹配→内核崩溃 3. 根文件系统缺失→kernel panic 调试建议: • 串口输出是救命稻草 • 逐级验证,不要跳步 • 先确认硬件,再查软件 • 用好 printch 和 earlycon

1.6 各阶段对比表

为了让你更清楚地看到每个阶段的特点,我整理了一张对比表。这张表在调试时特别有用,能帮你快速定位问题出在哪个环节。

阶段 运行位置 代码大小 主要任务 可修改性
BootROM 片内 ROM 几 KB 初始化时钟、加载 SPL 不可修改
SPL 片内 SRAM 几十 KB 初始化 DDR、加载 U-Boot 可修改(源码级)
U-Boot DDR 内存 几百 KB ~ 几 MB 加载内核、设备树、传递参数 可修改(源码级)
Linux 内核 DDR 内存 几 MB ~ 几十 MB 硬件初始化、进程管理、挂载根文件系统 可修改(源码级)
根文件系统 存储介质 几 MB ~ 几百 MB 提供用户空间程序、库、配置文件 可修改(文件级)

1.7 调试启动问题的基本思路

做 U-Boot 移植,说白了就是跟这条启动链打交道。问题可能出在任何一环。我个人习惯的调试思路是这样的:

  1. 先确认硬件:电源、时钟、复位信号对不对?串口能不能输出?
  2. 看 BootROM 阶段:启动介质选对了没有?SPL 有没有被正确加载?
  3. 看 SPL 阶段:DDR 初始化成功了吗?U-Boot 加载到内存后校验通过了吗?
  4. 看 U-Boot 阶段:U-Boot 能正常启动吗?能识别存储设备吗?能加载内核吗?
  5. 看内核阶段:内核解压成功了吗?设备树解析正确吗?根文件系统挂上了吗?
我曾经踩过的坑:有一次板子启动到一半就卡死了,串口没有任何输出。我以为是 U-Boot 的问题,折腾了两天。最后发现是电源纹波太大,导致 DDR 在高温下工作不稳定。所以,硬件问题永远是第一排查对象,别一上来就改代码。

1.8 为什么选择 Buildroot + U-Boot?

你可能要问,市面上这么多构建工具,为什么咱们这门课选 Buildroot?

  • 简单直接:Buildroot 的配置体系清晰,不像 Yocto 那么庞大复杂
  • 全链路覆盖:从交叉工具链、U-Boot、内核到根文件系统,一条命令全搞定
  • 适合调试:修改配置后重新编译很快,迭代效率高
  • 社区活跃:遇到问题容易找到解决方案

我个人做项目时,原型阶段基本都用 Buildroot。等产品定型了,再考虑换更复杂的构建系统。你想想看,调试阶段最重要的是什么?是快速验证想法。Buildroot 在这方面做得很好。