U-Boot 源码结构:顶层目录与关键子模块
拿到一份 U-Boot 源码,第一眼看到几十个目录,别慌。我刚开始接触 U-Boot 时,也对着 ls -l 的输出发过呆。后来慢慢摸清了门道——其实它的目录结构很有规律,说白了就是「平台无关的核心逻辑」和「平台相关的硬件适配」两条线。
这一章,我带你把顶层目录和几个关键子目录过一遍。你不需要记住每个文件,但得知道遇到问题时该去哪个目录翻代码。
顶层目录:一眼看穿骨架
U-Boot 的顶层目录大概有 20 多个。我挑几个最常用的说说:
| 目录/文件 | 作用 | 我常用的场景 |
|---|---|---|
board/ |
板级初始化代码,每个板子一个子目录 | 调新板子时,第一个改的就是这里 |
arch/ |
CPU 架构相关代码(ARM、RISC-V、x86 等) | 移植到新 CPU 时,必须动这里 |
common/ |
核心框架代码,命令解析、环境变量等 | 改环境变量逻辑时,常来翻 |
cmd/ |
所有命令行命令的实现 | 加自定义命令时,就在这里加文件 |
drivers/ |
外设驱动(串口、网卡、Flash、MMC 等) | 调驱动时,80% 的时间泡在这里 |
include/ |
全局头文件、配置宏定义 | 改配置时,configs/ 子目录是常客 |
lib/ |
通用库函数(字符串、CRC、加密等) | 写工具函数时,先看看这里有没有现成的 |
net/ |
网络协议栈(TFTP、NFS、DHCP 等) | 搞网络启动时,必看 |
fs/ |
文件系统支持(ext4、FAT、UBIFS 等) | 做 OTA 时,经常要调文件系统读写 |
tools/ |
主机端工具(mkimage、env 工具等) | 打包固件、制作环境变量镜像时用 |
board/ 和 include/configs/ 下的文件。这两个目录基本决定了板子能不能跑起来。
board/ 目录:板级定制的灵魂
board/ 目录下,每个厂商或板型都有一个子目录。比如 board/ti/am335x/、board/rockchip/evb_rk3399/。里面通常包含:
- 板级初始化代码(
.c和.S文件)—— 设置时钟、DDR、引脚复用 - 板级头文件 —— 定义板子特有的宏
- Kconfig 或 Makefile —— 编译控制
我记得有一次调一个新板子,DDR 死活初始化不过。折腾了两天,最后发现是 board/ 下的时序参数配错了。嗯,这种坑踩过一次就记住了。
board/ 里写通用逻辑。我曾经见过有人把网卡驱动直接塞进板级目录里,结果换板子时全得重写。驱动就该放 drivers/,板级目录只放「这块板子特有」的东西。
arch/ 目录:CPU 架构的根据地
arch/ 目录按 CPU 架构分:arch/arm/、arch/riscv/、arch/x86/ 等。每个架构下又分:
cpu/—— CPU 核的初始化(MMU、Cache、异常向量)include/—— 架构相关的头文件lib/—— 架构相关的库函数(比如 cache 操作、原子操作)mach-xxx/—— SoC 厂商的特定代码(比如mach-sunxi/、mach-imx/)
为什么要把架构和板级分开?你想想看,ARM 的 MMU 初始化逻辑,所有 ARM 芯片都能共用。但 DDR 的时序参数,每个板子都不一样。分层之后,复用性就好多了。
common/ 目录:U-Boot 的心脏
common/ 是 U-Boot 的核心框架。这里面的东西,你几乎每天都会打交道:
main.c—— 启动主流程,从board_init_r()到命令行循环cli.c/cli_hush.c—— 命令行解析器env_*.c—— 环境变量后端(env_nand.c、env_mmc.c、env_sf.c等)bootm.c—— 启动内核的核心逻辑image.c/image-fdt.c—— 镜像解析和 FDT 处理
我个人觉得,common/ 是理解 U-Boot 工作流程的最佳入口。你从 main.c 的 board_init_r() 开始读,一路跟下去,就能把整个启动流程串起来。
cmd/ 目录:命令的百宝箱
你在 U-Boot 命令行敲的每个命令,都能在 cmd/ 下找到对应的 .c 文件。比如:
| 命令 | 源文件 | 说明 |
|---|---|---|
printenv |
cmd/nvedit.c |
打印环境变量 |
setenv |
cmd/nvedit.c |
设置环境变量 |
saveenv |
cmd/nvedit.c |
保存环境变量到存储设备 |
tftp |
cmd/net.c |
TFTP 下载文件 |
mmc |
cmd/mmc.c |
MMC/SD 卡操作 |
sf |
cmd/sf.c |
SPI Flash 操作 |
bootm |
cmd/bootm.c |
启动内核镜像 |
如果你想加一个自定义命令,比如 ota_update,直接在 cmd/ 下新建一个 ota.c,然后用 U_BOOT_CMD 宏注册就行。我后面讲 OTA 实战时,会手把手带你写一个。
U_BOOT_CMD 或 U_BOOT_LONGHELP 宏注册。编译系统会自动扫描并链接。你不需要手动改 Makefile,除非你要加新的编译选项。
drivers/ 目录:硬件驱动的集散地
drivers/ 是 U-Boot 里最大的目录之一。它按设备类型分子目录:
drivers/serial/—— 串口驱动(调试输出全靠它)drivers/net/—— 网卡驱动(TFTP、NFS 的基础)drivers/mmc/—— MMC/SD 卡驱动drivers/mtd/—— NAND Flash、SPI Flash 驱动drivers/spi/—— SPI 控制器驱动drivers/i2c/—— I2C 控制器驱动drivers/usb/—— USB 主机/设备驱动
U-Boot 现在全面采用 Driver Model (DM)。说白了,就是给每个驱动定义一个 struct udevice,通过设备树来匹配和初始化。这样做的好处是:换板子时,只要改设备树,驱动代码基本不用动。
我曾经在一个项目里,把同一套网卡驱动用在了三块不同板子上。每块板子的 PHY 地址、复位引脚不一样,但驱动代码一个字没改——全在设备树里配的。这就是 DM 的魅力。
一张图看懂 U-Boot 源码结构
下面这张图,把 U-Boot 的源码分层和关键目录的关系画出来了。你可以把它当个地图用:
几个实用技巧
最后,分享几个我平时翻源码的小习惯:
- 找函数定义:用
grep -rn "function_name" *或cscope。U-Boot 代码量大,别硬翻。 - 看配置宏:在
include/configs/下找板子的头文件,比如am335x_evm.h。里面定义了所有板级配置。 - 加调试打印:在
common/或drivers/里加printf(),重新编译烧录。这是最笨但最有效的方法。 - 理解 Kconfig:U-Boot 现在用 Kconfig 管理配置。在
board/、arch/、drivers/下都有Kconfig文件。想加新功能,先看看对应的 Kconfig 有没有选项。
board/、arch/、common/、cmd/、drivers/ 这五个。等你需要调网络时,再去看 net/;需要搞文件系统时,再去看 fs/。一口吃不成胖子,做嵌入式更是这样。