U-Boot 源码结构:从顶层到底层的设计哲学

拿到一份 U-Boot 源码,第一眼看到几十个目录,很多人会懵。我刚开始接触时也一样,对着 ls 输出发了好一会儿呆。但别怕,这套结构其实很有章法。说白了,它就是一套「硬件抽象 + 平台分层」的经典实践。

今天我们就把它拆开来看。我会带着你,从顶层目录一路走到最底层的驱动代码。嗯,这里要注意:不是每个目录都要背下来,而是理解它为什么这么放。

核心思想:U-Boot 的目录结构遵循「三权分立」——架构无关的通用代码、架构相关的核心代码、板级特定的配置代码,各司其职。

顶层目录结构:一眼看穿全局

先看顶层。你执行 ls 后,会看到大约 20 多个目录和文件。我挑几个最关键的来说:

目录/文件 作用 我的评价
arch/ CPU 架构相关代码(ARM、RISC-V、x86 等) 移植新 CPU 时第一个要看的目录
board/ 具体板卡的初始化代码 每个项目几乎都要改这里
common/ 通用核心逻辑(命令解析、环境变量等) 最稳定的部分,基本不动
drivers/ 各种外设驱动(串口、网卡、存储等) 调试硬件问题时泡在这里
include/ 全局头文件和配置文件 configs/ 子目录放板级配置
lib/ 通用库函数(字符串、加密、压缩等) 很少需要改
net/ 网络协议栈 TFTP 下载、NFS 挂载都靠它
fs/ 文件系统支持(ext4、FAT、UBIFS 等) 加载内核镜像时必经之路
cmd/ 命令行命令的实现 每个命令一个文件,很清晰

我个人习惯,拿到一个新板子,先看 board/ 和 arch/,再翻 include/configs/ 下的头文件。这三个地方基本能告诉你这块板子是怎么启动的。

board 目录:板级定制的战场

board/ 目录是按厂商和板卡名组织的。比如:

board/
├── ti/
│   ├── am335x/
│   │   ├── board.c
│   │   ├── mux.c
│   │   └── Kconfig
│   └── omap5/
├── rockchip/
│   ├── evb_rk3399/
│   └── pinebook_pro/
└── my_company/
    └── my_board/

每个板卡目录里,通常有:

  • board.c:板级初始化函数,比如 board_init()、dram_init()
  • mux.c:引脚复用配置(ARM 平台常见)
  • Kconfig:板级的配置选项
  • MAINTAINERS:谁负责维护这块板子

我的经验:曾经接手一个项目,板子 DDR 参数配错了,死活起不来。最后发现是 board/ 下的 dram_init() 里时序参数写反了。从那以后,我每次移植新板子,第一件事就是对着原理图核对 board 目录里的初始化代码。

arch 目录:架构的灵魂

arch/ 目录按 CPU 架构分。以 ARM 为例:

arch/arm/
├── cpu/
│   ├── armv7/
│   ├── armv8/
│   └── arm11/
├── include/
│   └── asm/
├── lib/
│   ├── cache.c
│   ├── interrupts.c
│   └── stack.c
├── mach-*/          # 具体 SoC 厂商的代码
└── Kconfig

这里藏着 U-Boot 启动的最早期代码。比如 arch/arm/cpu/armv8/start.S,那是 CPU 复位后的第一条指令。我调试过几次启动失败的问题,最后都追到了这个汇编文件里。

为什么会这样?因为 arch/ 负责的是「架构级」的事情:

  • 设置异常向量表
  • 初始化 MMU 和 Cache
  • 设置栈指针
  • 跳转到 C 语言入口

这些代码一旦写错,板子就是「死给你看」。我曾经在移植 RISC-V 平台时,start.S 里一个跳转地址算错了,折腾了两天才找到问题。

common 目录:U-Boot 的「操作系统」

common/ 目录是 U-Boot 的核心逻辑所在。它不依赖具体硬件,提供的是「框架」:

common/
├── main.c           # 主循环
├── command.c        # 命令注册与分发
├── console.c        # 控制台 I/O
├── env_common.c     # 环境变量
├── image.c          # 镜像解析
├── bootm.c          # 启动内核的核心函数
└── cli.c            # 命令行交互

我个人觉得,common/ 是 U-Boot 里最值得精读的部分。尤其是 bootm.c,它实现了镜像加载的完整流程——从解析镜像头、校验、解压,到跳转到内核入口。我们后面几章会反复提到它。

注意:不要随意修改 common/ 下的代码。这些文件被所有板卡共享,改一个地方可能影响几十个平台。我见过有人为了加一个调试打印,改了 main.c,结果其他板子启动时多输出了不该有的信息。

drivers 目录:硬件世界的桥梁

drivers/ 是 U-Boot 里最大的目录之一。它按设备类型组织:

drivers/
├── serial/          # 串口驱动
├── net/             # 网卡驱动
├── mmc/             # SD/eMMC 驱动
├── spi/             # SPI 控制器驱动
├── i2c/             # I2C 控制器驱动
├── gpio/            # GPIO 驱动
├── usb/             # USB 主机/设备驱动
├── mtd/             # NAND Flash、SPI Flash 驱动
└── video/           # 显示驱动

每个子目录里,通常有一个 Kconfig 和多个驱动文件。U-Boot 的驱动模型(Driver Model)从 2014 年开始引入,现在已经是主流。它把驱动分成了三步:

  1. 绑定(Bind):设备树节点和驱动匹配
  2. 探测(Probe):初始化硬件
  3. 操作(Operate):提供读写接口

我在调试网卡驱动时,就靠这套模型一步步定位问题。先看设备树里节点对不对,再看驱动有没有被 probe 成功,最后才去查寄存器配置。

一张图看懂整体结构

下面这张 SVG 图,把 U-Boot 源码的核心目录和它们之间的关系画了出来。你可以把它当作「导航地图」:

U-Boot 源码核心目录结构图 U-Boot 源码核心目录结构 U-Boot 根目录 arch/ board/ common/ drivers/ arm/ riscv/ ti/am335x/ bootm.c serial/ 说明:arch/ 负责 CPU 架构,board/ 负责板级硬件,common/ 提供通用逻辑,drivers/ 封装外设操作。 四者通过 Kconfig 和 Makefile 串联,最终生成一个完整的 U-Boot 镜像。 分层设计:架构无关 → 架构相关 → 板级特定

其他重要目录

除了上面四个核心目录,还有几个也值得了解:

  • include/configs/:板级配置头文件。比如 am335x_evm.h 里定义了 DDR 大小、环境变量位置、默认启动命令等。我每次移植新板子,都要在这里改几十行宏定义。
  • lib/:通用库。比如 lib/zlib/ 用于解压 gzip 镜像,lib/crypto/ 用于校验镜像签名。
  • net/:网络协议栈。U-Boot 的 TFTP 客户端就在这里实现。调试网络启动时,我经常要加打印看 net/ 里的数据包处理流程。
  • fs/:文件系统支持。从 FAT32 读内核镜像,或者从 ext4 根文件系统加载设备树,都靠这里的代码。
  • cmd/:每个命令一个文件。比如 cmd/bootm.c 实现了 bootm 命令,cmd/load.c 实现了 loadb、loadx 等下载命令。

一个小技巧:想快速了解一个命令的实现,就去 cmd/ 下找对应的 .c 文件。比如 cmd/load.c 里,你能看到 U-Boot 是怎么通过串口接收二进制数据的——包括校验、超时、重传等细节。

总结一下

U-Boot 的源码结构,说白了就是一套「分层 + 模块化」的设计。顶层目录划分了职责边界,arch/ 管 CPU,board/ 管板子,common/ 管逻辑,drivers/ 管外设。它们之间通过 Kconfig 和 Makefile 串联起来。

你想想看,这种设计的好处是什么?移植一个新板子时,你只需要改 board/ 和 arch/ 下的少量代码,其他部分基本不用动。这就是 U-Boot 能支持上千种板卡的原因。