1. U-Boot 概述:从历史到启动流程

大家好,我是你们的讲师。今天咱们开始聊 U-Boot,这个嵌入式世界里几乎绕不开的 Bootloader。说实话,我入行那会儿,U-Boot 还没现在这么流行,大家还在用各种五花八门的私有 Bootloader。后来项目越做越复杂,才体会到 U-Boot 的好。

这一章,我们先搭个框架。搞清楚 U-Boot 是什么,它凭什么在嵌入式系统里站稳脚跟,以及它上电后到底干了些什么。嗯,这部分偏概念,但很重要——你想想看,连启动流程都不清楚,后面怎么调镜像加载?

1.1 U-Boot 的历史:从 PPCBoot 到今天的行业标准

U-Boot 的全称是 Universal Boot Loader。它最早脱胎于 1999 年的 PPCBoot,当时只支持 PowerPC 架构。后来 ARM 架构崛起,社区把 PPCBoot 和 ARMBoot 等项目合并,2002 年正式更名为 U-Boot。

我个人习惯把 U-Boot 的发展分成三个阶段:

阶段 时间 特点
萌芽期 1999 - 2002 PPCBoot 主导,架构单一
扩张期 2002 - 2010 支持 ARM、MIPS、x86,社区活跃
成熟期 2010 - 至今 设备树、FIT 镜像、Driver Model 全面普及

我记得 2012 年左右,我接手一个基于 TI OMAP 的项目。当时用的 U-Boot 版本还是 2010.06,连设备树支持都不完整。为了适配一块新的 NAND Flash,我硬啃了三天源码。那时候就在想,要是社区早点把驱动模型统一了该多好。现在回头看,U-Boot 的演进确实解决了很多当年的痛点。

1.2 U-Boot 在嵌入式系统中的地位

嵌入式系统上电后,CPU 会从一个固定的地址开始执行代码。这个地址通常放的是 BootROM,它很小,只够初始化最基本的硬件,然后去找下一级启动代码。U-Boot 就扮演这个「下一级」的角色。

说白了,U-Boot 是连接硬件和操作系统的桥梁。它负责:

  • 硬件初始化:时钟、DDR、串口、存储控制器
  • 镜像加载:从 Flash、网络、SD 卡读取内核和设备树
  • 启动引导:跳转到内核入口,把控制权交给操作系统

你可能会问:为什么不用 Linux 自带的 EFI stub?或者直接用 Coreboot?

原因很简单:U-Boot 对嵌入式硬件的支持最全。从古老的 ColdFire 到最新的 RISC-V,从 8KB 的 NOR Flash 到 4GB 的 eMMC,U-Boot 几乎都能搞定。我在项目中遇到过最极端的情况——一块只有 64KB SRAM 的 MCU,U-Boot 硬是通过 XIP(原地执行)方式跑起来了。这种灵活性,其他 Bootloader 很难做到。

核心观点:U-Boot 不是最快的 Bootloader,也不是最小的 Bootloader,但它是最通用的。在嵌入式领域,「通用」往往比「极致」更重要。

1.3 U-Boot 的启动流程概览

U-Boot 的启动流程,我习惯把它拆成两个阶段:SPL(Secondary Program Loader)和 U-Boot proper。早期 U-Boot 是单阶段启动,后来为了适应越来越大的镜像体积和更严格的启动时间要求,社区引入了 SPL 机制。

下面这张图展示了典型的启动流程:

U-Boot 启动流程概览 上电复位 BootROM SPL(第一阶段) U-Boot proper(第二阶段) 加载内核 + DTB 跳转到内核 SPL 负责最小初始化,U-Boot proper 提供完整命令行和加载能力

每个阶段的具体职责如下:

阶段 代码位置 主要任务 典型大小
BootROM 芯片内部 ROM 初始化时钟、检测启动介质、加载 SPL 4KB - 32KB
SPL SPL 镜像 初始化 DDR、加载 U-Boot proper 16KB - 64KB
U-Boot proper u-boot.bin 完整外设驱动、命令行、网络、文件系统 200KB - 1MB

避坑指南:我曾经在一个项目里把 SPL 做得太大,超过了芯片 BootROM 的加载限制。结果板子死活起不来,查了两天才发现是 SPL 编译时开了太多驱动。记住:SPL 的原则是「够用就行」,别贪多。

1.4 启动流程中的关键决策点

U-Boot 启动过程中有几个关键决策点,直接影响镜像加载的成功率:

  1. 启动介质优先级:SPL 从哪加载 U-Boot proper?是 NAND、SD 卡还是网络?这个顺序由 boot_device 或 spl_boot_device() 决定。
  2. 环境变量加载:U-Boot proper 启动后,会从存储介质加载 env。如果环境变量损坏,会回退到默认值。我遇到过环境变量校验失败导致启动卡死的情况,后来加了冗余备份才解决。
  3. bootcmd 执行:环境变量加载完成后,U-Boot 自动执行 bootcmd。这个变量定义了最终的启动命令,比如 bootm、bootz 或 ext4load。

注意:很多新手以为 U-Boot 启动后一定会进命令行。其实如果 bootdelay 设为 0,U-Boot 会直接执行 bootcmd,根本不给你敲命令的机会。调试阶段建议把 bootdelay 设成 3 秒以上。

1.5 本章小结

这一章我们聊了 U-Boot 的来龙去脉、它在嵌入式系统里的角色,以及上电后的启动流程。说白了,U-Boot 就是一个「硬件初始化 + 镜像搬运工」。SPL 负责把 U-Boot proper 搬进内存,U-Boot proper 再把内核搬进内存,最后跳转执行。