跳到主要内容

嵌入式系统与 Bootloader:U-Boot 启动入门

当你按下开发板的电源键时,Linux 并不会立刻开始运行。在内核接管系统之前,处理器、内存和存储设备需要先进入可用状态,内核及其启动参数也需要被放到合适的位置。连接“设备上电”和“操作系统运行”的这段过程,就是本章要认识的系统启动过程。

本教程以 Mainline U-Boot v2026.07 为基准。本章介绍通用启动概念,不依赖某一款开发板。

学习目标

阅读本章后,你将能够:

  • 理解设备上电后为什么不能直接运行 Linux
  • 了解 Bootloader 的基本职责与边界
  • 区分 Boot ROM、Bootloader、Linux 内核和根文件系统
  • 理解“多阶段启动”的含义
  • 判断 U-Boot 在典型启动链中的大致位置

前置知识

具备基本的嵌入式或 Linux 使用经验即可。本章不要求你已经用过 U-Boot。

1. 从一个看似简单的问题开始

假设一块嵌入式 Linux 开发板上已经保存了 Linux 内核和根文件系统。当开发板上电后,处理器为什么不能直接运行 Linux?

因为此时系统还没有准备好。

在一个典型的嵌入式 Linux 系统中,刚上电时通常存在以下问题:

  • DRAM 尚未完成初始化,不能直接作为运行内存使用
  • Linux 内核仍保存在 eMMC、SD 卡、SPI Flash 或其他存储介质中
  • 处理器只知道复位后的第一条指令地址,并不知道内核文件在哪里
  • 时钟、引脚复用和存储控制器等硬件可能尚未配置
  • Linux 还不知道设备具有多少内存、使用哪些外设
  • 内核启动参数和设备树尚未准备好

因此,系统需要先运行一段规模较小、所需条件更少的程序,由它逐步初始化硬件并加载后续程序。这个过程就像搭梯子:当前阶段只需要具备运行下一个阶段的能力,而下一个阶段再继续建立更完整的运行环境。

这种由小程序逐步加载大程序的过程称为 Bootstrapping,计算机启动过程中的“Boot”一词也由此而来。

2. 处理器上电后从哪里开始运行

处理器上电或复位后,会进入架构和芯片规定的复位状态,并从指定的复位入口开始取指执行。

在许多 SoC 中,最先执行的是芯片内部固化的一段只读程序,通常称为 Boot ROM。它由芯片厂商在设计和制造 SoC 时写入;对产品开发者来说,一般不能像改写 Flash 那样修改它。

Boot ROM 的能力通常比较有限,常见任务包括:

  1. 读取启动引脚、熔丝或其他启动配置
  2. 判断从 eMMC、SD 卡、SPI Flash、USB 或网络等哪个位置启动
  3. 从启动介质读取下一阶段镜像
  4. 在安全启动系统中验证镜像
  5. 将镜像放入片上 SRAM,并跳转执行

Boot ROM 通常不会直接完成整个 Linux 启动过程。它更像启动链的第一个搬运者:在资源非常有限的环境中,找到并运行一个能力更强的后续程序。

Boot ROM 的具体行为由 SoC 决定。启动介质、镜像格式、读取偏移和校验方法都可能不同,应以芯片参考手册为准。

3. 什么是 Bootloader

Bootloader 通常翻译为“启动加载程序”或“引导加载程序”。它泛指在目标程序或操作系统运行之前执行,负责建立运行条件并将控制权交给下一阶段的软件。

Bootloader 并不一定是单个程序。一个复杂系统可能包含多个 Bootloader 或固件阶段,每个阶段只完成一部分任务。

一个 Bootloader 可能承担以下职责:

职责说明
初始化必要硬件配置时钟、DRAM、串口、存储控制器等启动所需硬件
选择启动来源根据启动顺序、按键、环境变量或升级状态选择启动介质
加载镜像将下一阶段固件、Linux 内核、设备树等加载到内存
验证镜像检查镜像格式、完整性、哈希值或数字签名
传递系统信息向下一阶段传递设备树、启动参数和内存布局等信息
提供调试能力提供串口输出、命令行、内存查看和设备访问能力
处理异常启动在启动失败时进入备用系统、Recovery 或升级模式
移交控制权设置必要的处理器状态,然后跳转到下一阶段入口

并不是每一个 Bootloader 都必须实现表中的全部功能。功能范围取决于处理器架构、硬件资源、启动链设计和产品需求。

4. 为什么启动过程经常分成多个阶段

启动初期可用的资源很少,而完整 Bootloader 的功能可能很多,这形成了一个矛盾:

  • 要运行功能完整的 Bootloader,通常需要 DRAM
  • 要初始化 DRAM,又必须先运行一段代码

解决方法是在片上 SRAM 中先运行一个更小的早期加载程序,由它初始化 DRAM,再把完整 Bootloader 加载到 DRAM 中运行。

一个常见的嵌入式 Linux 启动过程如下:

各阶段的典型分工如下:

阶段运行位置主要任务
Boot ROM芯片内部 ROM识别启动方式,加载并验证下一阶段
早期加载阶段片上 SRAM完成最小硬件初始化,尤其是初始化 DRAM
完整 Bootloader通常位于 DRAM识别设备,选择启动方案,加载操作系统
Linux 内核DRAM初始化内核子系统和设备驱动,挂载根文件系统
用户空间根文件系统启动 init,运行系统服务和应用程序

这里的“早期加载阶段”和“完整 Bootloader”是按职责划分的通用名称。在不同平台中,它们可能叫作 SPL、TPL、TF-A、FSBL、OpenSBI 或厂商 Loader,也可能以不同顺序组合。

备注

U-Boot 里的 SPL(Secondary Program Loader)、TPL(Tertiary Program Loader)是项目内的命名约定,并不等于芯片手册里的“第几级 Bootloader”。分析具体平台时,仍要看它运行在哪里、初始化了什么、加载了谁。

因此,学习启动过程时不要只记文件名,而应先判断:

当前阶段运行在哪里?它初始化了什么?它加载了谁?最后把控制权交给了谁?

更细的分阶段拆解、日志与排障,见从上电到 Linux 的启动过程

5. MCU 和嵌入式 Linux 系统的启动有何不同

并不是所有嵌入式系统都需要复杂的 Bootloader。

许多 MCU 使用内部 Flash 和片上 SRAM。处理器复位后,可以直接从内部 Flash 中读取中断向量,运行启动代码,然后进入应用程序的 main() 函数。

复位 → 启动代码 → main()

复杂的嵌入式 Linux 系统通常使用 SoC、外部 DRAM 和大容量存储设备。Linux 内核体积更大,还需要设备树、启动参数和根文件系统,因此启动链通常更长:

复位 → Boot ROM → 早期加载程序 → Bootloader → Linux 内核 → 用户空间

两者之间并没有绝对界限。一些 MCU 也会使用 Bootloader 实现固件升级和安全验证;一些经过特殊设计的系统也可能缩短 Linux 启动链。关键区别在于系统启动需要完成多少准备工作,而不只是处理器被称为 MCU 还是 SoC。

6. U-Boot 位于启动链的什么位置

U-Boot 是嵌入式系统中常见的开源 Bootloader。它的首要任务是加载并启动操作系统,同时也提供设备初始化、命令行、环境变量、网络启动、镜像验证和硬件调试等能力。

在一个相对简单的 ARM 平台上,启动链可能是:

Boot ROM → U-Boot SPL → U-Boot proper → Linux

其中:

  • U-Boot SPL 是精简的早期加载阶段,通常负责初始化 DRAM 并加载后续镜像
  • U-Boot proper 是功能相对完整的 U-Boot,能够提供命令行并加载 Linux

在一个 ARM64 平台上,启动链也可能是:

Boot ROM → 早期加载程序 → TF-A → U-Boot → Linux

在 RISC-V 平台上,还可能出现 OpenSBI:

Boot ROM → 早期加载程序 → OpenSBI → U-Boot → Linux

本教程的主实验平台是 QEMU ARM64 virt。那里的路径往往更短,例如由 QEMU 直接载入 U-Boot proper,再启动 Linux——不少真实板上的 Boot ROM、DDR 初始化和平台固件步骤被模拟器简化了。

这些只是帮助理解的简化示例。有些平台由 U-Boot SPL 加载 TF-A、OpenSBI 和 U-Boot proper;有些完全使用厂商固件完成早期初始化;还有些平台根本不使用 U-Boot。

由此可以得到两个重要结论:

  1. U-Boot 通常不是设备上电后运行的第一段程序
  2. U-Boot 也不一定独自完成所有硬件初始化

7. U-Boot 为 Linux 准备什么

以常见的 AArch64 Linux 系统为例,在跳转到内核前,启动链至少需要完成几项关键工作:

  • 初始化 Linux 将要使用的 RAM
  • 准备描述硬件信息的设备树
  • 将内核镜像放到符合要求的内存位置
  • 设置必要的处理器状态并跳转到内核入口

在实际系统中,U-Boot 还经常负责加载内核镜像、设备树、可选的 initramfs,并设置 bootargs 等启动参数,再通过类似 booti 的命令移交控制权。

提示

U-Boot 能从 FAT、EXT4 等文件系统读文件,不等于它会替 Linux 挂载最终的根文件系统。通常:U-Boot 加载启动文件,Linux 挂载 rootfs 并启动用户空间。寄存器约定、设备树修正和交接细节见从上电到 Linux 的启动过程

8. Bootloader 的职责边界

U-Boot 功能丰富,很容易让初学者产生一种误解:既然它能访问很多硬件,是不是应该在 U-Boot 中把所有设备都初始化好?

通常没有必要。

Bootloader 的目标是可靠、快速地把系统带到下一个阶段。它只需要初始化启动过程真正使用的设备。例如:

  • 从 eMMC 启动时,需要初始化相关时钟、引脚和 eMMC 控制器
  • 通过 TFTP 加载内核时,需要初始化网络控制器
  • 只使用串口输出时,没有必要提前初始化摄像头

Linux 启动后,会通过自己的驱动重新管理大部分硬件。过度初始化不仅会延长启动时间,还可能让 Bootloader 与内核之间产生状态交接问题。

因此,可以用一句话概括 Bootloader 的职责边界:

准备下一阶段所需的最小环境,然后尽快、可靠地移交控制权。

9. PC 与嵌入式系统的启动差异

PC 和嵌入式设备都需要完成从上电到操作系统运行的过程,但两者的软硬件生态不同。

对比项PC嵌入式系统
早期固件通常是 BIOS 或 UEFI通常从 SoC Boot ROM 开始
硬件标准化程度相对较高平台差异较大
常见操作系统加载程序GRUB、Windows Boot Manager 等U-Boot、厂商 Bootloader 等
启动介质SSD、硬盘、USB、网络等eMMC、SD、SPI Flash、NAND、网络等
硬件描述方式常见 ACPI常见设备树
配置重点通用设备枚举和操作系统选择DDR、引脚、存储和板级硬件适配

这也是为什么 U-Boot 教程不能只讲几条启动命令。要真正理解 U-Boot,还需要理解 SoC 启动方式、设备树、存储布局和板级硬件之间的关系。

10. 把启动想成一条可排查的链

遇到“设备无法启动”时,不必先猜具体命令写错了,可以先按阶段缩小范围:

  1. 上电与复位是否正常?
  2. Boot ROM 是否选对了启动介质?
  3. 早期加载程序是否跑起来(有无串口输出)?
  4. 是否已经进入 U-Boot 提示符?
  5. U-Boot 能否找到并加载内核与设备树?
  6. 内核是否开始打印日志、能否挂载 rootfs?

例如:串口完全没有输出,更可能卡在供电、Boot ROM 或早期串口初始化之前;已经出现 =>,通常说明 DRAM 和调试串口至少基本可用。更完整的日志对照与故障表,见从上电到 Linux 的启动过程

11. 常见认识误区

误区一:Boot ROM 就是 U-Boot

不是。Boot ROM 通常固化在 SoC 内部,由芯片厂商提供;U-Boot 是后续运行的可配置软件。

误区二:U-Boot 是 Linux 的一部分

不是。U-Boot 和 Linux 是两个独立项目。U-Boot 先运行,完成准备后再启动 Linux。

误区三:U-Boot 一定是第一级 Bootloader

不一定。U-Boot 前面可能还有 Boot ROM、厂商 Loader、TF-A 或其他固件。

误区四:U-Boot 负责启动所有用户程序

通常不是。U-Boot 启动 Linux 内核;Linux 挂载根文件系统并启动 init,再由用户空间启动系统服务和应用程序。

本章小结

嵌入式 Linux 启动是一个逐步建立运行环境的过程。Boot ROM 从有限资源出发加载早期程序;早期程序初始化 DRAM;完整 Bootloader 准备内核与启动参数;最后 Linux 接管硬件并启动用户空间。U-Boot 是这条链中的常见组成部分,但它既不是 Boot ROM,也不一定独自完成全部启动工作。

思考与练习

  1. 为什么完整 U-Boot 通常不能直接在片上 SRAM 中运行?
  2. Boot ROM、U-Boot 和 Linux 内核分别由谁提供?它们通常存放在哪里?
  3. 如果已经进入 U-Boot 命令行,可以初步判断哪些硬件已经工作?
  4. 查阅你手边一块开发板的资料,画出它从上电到 Linux 的启动链(名称以板卡文档为准)。

参考资料