2. U-Boot 源码结构解析:顶层目录结构、board、arch、common、drivers、cmd 等关键目录功能分析
拿到一份 U-Boot 源码,第一反应是什么?我刚开始接触时,面对几百个文件夹确实有点懵。但别怕,U-Boot 的目录结构其实很有章法。说白了,它就是一套为嵌入式系统量身定制的「引导加载程序框架」。今天我就带你把它拆开看看。
2.1 顶层目录结构一览
我们先从顶层开始。你随便下载一个 U-Boot 版本(比如 v2023.10),解压后看到的顶层目录大概是这样:
├── arch/ # 体系结构相关代码
├── board/ # 板级支持代码
├── cmd/ # 命令行命令实现
├── common/ # 通用核心代码
├── configs/ # 板级默认配置文件
├── doc/ # 文档
├── drivers/ # 设备驱动
├── dts/ # 设备树源文件
├── fs/ # 文件系统支持
├── include/ # 头文件
├── lib/ # 库函数
├── net/ # 网络协议栈
├── scripts/ # 构建脚本
├── tools/ # 主机端工具
├── Makefile # 顶层 Makefile
└── Kconfig # 顶层配置菜单
嗯,这里要注意:不是每个目录你都需要深入。实际调试中,你 80% 的时间会泡在 arch/、board/、cmd/ 和 drivers/ 这四个目录里。其他的,知道它们存在就行。
2.2 arch/ 目录:体系结构的灵魂
arch/ 目录存放的是与 CPU 架构紧密相关的代码。U-Boot 支持 ARM、RISC-V、x86、MIPS、PowerPC 等主流架构。每个架构下又细分了 SoC 级代码。
以 ARM 为例,它的结构是这样的:
arch/arm/
├── cpu/ # CPU 核初始化(如 cortex-a7、cortex-a53)
├── dts/ # ARM 设备树文件
├── include/ # 架构级头文件
├── lib/ # 架构级库函数
├── mach-*/ # 各 SoC 厂商代码(mach-sunxi、mach-rockchip 等)
└── Kconfig # 架构级配置
我个人习惯,拿到一块新板子,第一件事就是去 arch/arm/cpu/ 下看它的 CPU 初始化流程。为什么?因为 U-Boot 启动的第一条指令就在这里。你想想看,如果 CPU 都没跑起来,后面全是白搭。
关键点: arch/arm/cpu/ 下的 start.S 是 U-Boot 的入口文件。它负责设置异常向量表、初始化 CPU 模式、关闭 MMU 和 Cache,然后跳转到 board_init_f()。我在调试一块 i.MX6ULL 板子时,遇到过启动卡死,最后发现是 start.S 中中断向量表地址配置错了——这种问题,不看源码根本找不到。
2.3 board/ 目录:板级定制的战场
board/ 目录是板级支持的核心。每个板子在这里都有一个专属文件夹。比如:
board/
├── freescale/
│ └── mx6ullevk/ # i.MX6ULL 评估板
├── rockchip/
│ └── evb_rk3588/ # RK3588 评估板
├── ti/
│ └── am335x/ # AM335x 系列
└── ...
每个板级目录下通常包含:
| 文件/目录 | 作用 |
|---|---|
board.c |
板级初始化函数,如 board_init()、dram_init() |
Kconfig |
板级配置选项 |
MAINTAINERS |
维护者信息 |
Makefile |
编译规则 |
imximage.cfg |
i.MX 系列专用的镜像头配置 |
我曾经踩过一个坑:某款基于 RK3288 的产品,DDR 参数在 board/rockchip/evb_rk3288/ 下配错了,导致 4GB 内存只认到 3GB。排查了整整两天,最后发现是 dram_init() 里 bank 数量写死了。所以说,板级目录是你调试硬件问题的第一站。
2.4 common/ 目录:U-Boot 的「操作系统」
common/ 目录存放的是 U-Boot 的核心框架代码。它不依赖具体硬件,是 U-Boot 的「操作系统层」。
common/
├── main.c # U-Boot 主循环
├── command.c # 命令注册与分发
├── console.c # 控制台 I/O
├── board_r.c # 重定位后的板级初始化
├── board_f.c # 重定位前的板级初始化
├── cli.c # 命令行交互
├── env_common.c # 环境变量管理
└── ...
这里我要重点提一下 board_f.c 和 board_r.c。它们是 U-Boot 初始化的两个阶段:
- board_f.c:在 Flash/ROM 中执行,完成早期初始化(时钟、串口、DRAM 初始化),然后将自身重定位到 DRAM。
- board_r.c:在 DRAM 中执行,完成剩余初始化(设备驱动、文件系统、网络栈),最后进入主循环。
调试技巧: 如果你在 U-Boot 启动过程中卡住了,可以在 board_f.c 和 board_r.c 的初始化函数列表里加 printf() 定位。我习惯在 init_sequence_f[] 数组的每个函数入口打印一条信息,这样一眼就能看出是哪个阶段出了问题。
2.5 cmd/ 目录:你敲的每条命令都在这里
你在 U-Boot 命令行敲的 printenv、bootm、tftp,它们的实现都在 cmd/ 目录下。
cmd/
├── bootm.c # 启动内核镜像
├── nand.c # NAND Flash 操作
├── mmc.c # MMC/SD 卡操作
├── net.c # 网络命令
├── usb.c # USB 命令
├── fat.c # FAT 文件系统命令
├── ext4.c # ext4 文件系统命令
├── gpio.c # GPIO 操作
├── i2c.c # I2C 操作
└── ...
每个命令的实现都遵循一个固定模式:
static int do_mycmd(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
{
// 命令处理逻辑
return 0;
}
U_BOOT_CMD(
mycmd, // 命令名
3, // 最大参数个数
1, // 是否重复执行
do_mycmd, // 处理函数
"命令简短描述",
"命令详细用法"
);
你想想看,如果你想给 U-Boot 加一个自定义命令,比如读取某个传感器的值,只需要在 cmd/ 下新建一个文件,用 U_BOOT_CMD 宏注册就行。我在项目中就经常这么干——调试硬件时,临时加一条命令读寄存器,比反复烧写固件快多了。
2.6 drivers/ 目录:设备驱动的汪洋
drivers/ 是 U-Boot 中最大的目录,没有之一。它按设备类型组织:
drivers/
├── serial/ # 串口驱动
├── spi/ # SPI 驱动
├── i2c/ # I2C 驱动
├── mmc/ # MMC/SD 卡驱动
├── net/ # 网卡驱动
├── usb/ # USB 驱动
├── gpio/ # GPIO 驱动
├── pinctrl/ # 引脚控制驱动
├── clk/ # 时钟驱动
├── mtd/ # 存储设备驱动(NAND、NOR、SPI Flash)
└── ...
U-Boot 的驱动模型(Driver Model,简称 DM)从 2014 年开始引入,现在已经是主流。它的核心思想是:驱动 = 设备 + 驱动 + uclass。
DM 三要素:
- uclass:设备类,定义该类设备的通用接口(如
serial_ops、mmc_ops) - driver:具体驱动,实现 uclass 定义的接口
- device:设备实例,绑定到具体的硬件
举个例子,串口驱动:uclass 定义 serial_putc()、serial_getc() 接口;driver 实现具体 UART 控制器的读写;device 对应板子上的某个 UART 端口。
我记得有一次调试 SPI Flash 驱动,死活读不到设备 ID。后来发现是 drivers/spi/ 下的驱动没有正确匹配设备树中的 compatible 字符串。U-Boot 的 DM 依赖设备树进行驱动绑定,设备树里写错了,驱动就加载不上。这个坑我踩过不止一次。
2.7 其他关键目录
| 目录 | 作用 | 我的建议 |
|---|---|---|
configs/ |
板级 defconfig 文件,如 mx6ullevk_defconfig |
改配置先看这里,别直接改 Kconfig |
dts/ |
设备树源文件(.dts、.dtsi) | 硬件描述全在这,调试驱动必备 |
include/ |
全局头文件,包括 configs/ 下的板级配置头文件 |
老版本 U-Boot 的配置主要靠头文件宏定义 |
lib/ |
通用库函数(字符串、数学、CRC 等) | 一般不需要动,但调试时可以用 lib/strto.c 里的函数 |
net/ |
网络协议栈(TFTP、NFS、DHCP 等) | 网络启动失败时,来这里看协议实现 |
2.8 知识结构图:U-Boot 源码目录关系
下面这张图展示了 U-Boot 各目录之间的依赖关系。你可以看到,arch/ 和 board/ 是硬件相关的底层,common/ 是核心框架,cmd/ 和 drivers/ 是功能实现,它们共同支撑起 U-Boot 的启动流程。
2.9 我的调试经验总结
最后,分享几个我在实际项目中积累的经验:
避坑指南:
- 不要直接修改顶层 Makefile:板级配置应该在
configs/下的 defconfig 文件中修改,或者通过make menuconfig配置。 - 设备树优先:新版本的 U-Boot 强烈依赖设备树。如果你发现某个驱动没加载,先去
dts/下检查设备树节点是否匹配。 - 善用 git blame:遇到看不懂的代码,用
git blame看看是谁写的、为什么这么写。我经常通过 commit message 找到问题的根源。 - 从 board/ 入手:拿到一块新板子,先看
board/目录下的代码,再结合configs/下的 defconfig,基本就能摸清它的硬件配置。