3. U-Boot源码结构解析:顶层目录、board、arch、common、drivers、cmd等关键目录作用
说实话,我第一次完整看U-Boot源码时,头是大的。几千个文件,几十个目录,从哪下手?
后来我养成了一个习惯:先看顶层目录,再按功能模块拆解。这就像你拿到一张地图,先看整体轮廓,再找具体街道。今天我就带你走一遍U-Boot的源码结构,把每个关键目录的作用讲透。
核心观点:U-Boot的目录结构遵循「架构-板级-功能」三层分离原则。理解了这个分层,你就能快速定位任何功能代码的位置。
3.1 顶层目录:U-Boot的骨架
先看顶层。你解压U-Boot源码后,第一眼看到的这些目录和文件,就是整个项目的骨架。
| 目录/文件 | 作用 | 我的经验 |
|---|---|---|
arch/ |
CPU架构相关代码(ARM、RISC-V、x86等) | 移植新芯片时,这里改得最多 |
board/ |
具体板级支持代码 | 每个产品都有自己的board目录 |
common/ |
通用核心功能(环境变量、命令解析等) | 调试启动流程时经常光顾 |
cmd/ |
命令行命令实现 | 想加新命令?来这里 |
drivers/ |
设备驱动(串口、网卡、Flash等) | 驱动适配的主战场 |
include/ |
头文件和配置文件 | 所有宏定义、结构体声明都在这里 |
lib/ |
通用库函数(字符串、数学、加密等) | 我经常在这里找现成的工具函数 |
net/ |
网络协议栈(TFTP、NFS、DHCP等) | OTA升级时,这里就是核心 |
fs/ |
文件系统支持(ext4、FAT、UBIFS等) | 从Flash读文件时用到 |
tools/ |
主机端工具(mkimage等) | 制作U-Boot镜像时必用 |
嗯,这里要注意:顶层目录的命名非常直观。arch就是架构,board就是板子,drivers就是驱动。你不需要死记硬背,用多了自然就熟了。
3.2 arch/目录:CPU架构的根据地
arch目录下按CPU架构分了子目录。我主要做ARM平台,所以最常看的是arch/arm/。
arch/arm/
├── cpu/ # CPU核心代码(启动、异常处理)
│ ├── armv7/ # Cortex-A7/A8/A9等
│ ├── armv8/ # Cortex-A53/A72等
│ └── ...
├── include/ # 架构相关头文件
├── lib/ # 架构通用函数(cache、MMU等)
├── mach-*/ # 厂商/SoC相关代码
│ ├── mach-sunxi/ # 全志
│ ├── mach-rockchip/ # 瑞芯微
│ └── ...
└── dts/ # 设备树源文件
避坑指南:我曾经在移植U-Boot到新芯片时,花了整整两天找启动代码。后来才发现,arch/arm/cpu/armv8/start.S才是真正的入口点。记住:U-Boot的入口在arch目录下,不在board目录。
为什么arch这么重要?因为CPU架构决定了启动流程。ARM有ARM的启动方式,RISC-V有RISC-V的玩法。你想想看,如果连CPU怎么复位、怎么跳转都不清楚,后面的调试根本没法做。
3.3 board/目录:板级定制的战场
board目录存放的是具体板卡的适配代码。每个板子一个子目录,里面包含板级初始化、内存配置、外设引脚复用等。
board/
├── mycompany/
│ └── myboard/
│ ├── Kconfig # 板级配置选项
│ ├── Makefile # 编译规则
│ ├── myboard.c # 板级初始化函数
│ ├── myboard.h # 板级头文件
│ └── lowlevel_init.S # 低层初始化(汇编)
└── ...
我个人习惯是:拿到一个新板子,先看board目录下的代码。因为这里记录了板子的所有硬件细节——DDR容量、Flash型号、网卡芯片、串口引脚……这些信息在调试时太关键了。
注意:board目录和arch目录有明确分工。arch管CPU怎么跑,board管板子怎么配。别把板级代码塞到arch里,也别把架构代码放到board里。这个原则一旦打破,后续维护就是噩梦。
3.4 common/目录:U-Boot的心脏
common目录是U-Boot的核心功能模块。这里没有硬件相关的代码,全是通用逻辑。
| 文件 | 功能 | 调试时关注点 |
|---|---|---|
main.c |
U-Boot主循环(命令读取和执行) | 启动卡住时,先看这里 |
board_r.c |
板级运行时初始化(第二阶段) | 外设初始化顺序 |
board_f.c |
板级早期初始化(第一阶段) | 串口、时钟等早期配置 |
cli.c |
命令行接口 | 命令解析逻辑 |
env_*.c |
环境变量存储(env_nand.c、env_mmc.c等) | 环境变量保存失败时排查 |
说白了,common目录就是U-Boot的大脑。它负责把arch、board、drivers这些「四肢」协调起来。你在启动日志里看到的每一行输出,几乎都能在common目录里找到源头。
3.5 cmd/目录:命令的百宝箱
你在U-Boot命令行敲的每个命令,实现都在cmd目录里。
cmd/
├── bootm.c # bootm命令(启动内核)
├── nand.c # nand命令(NAND Flash操作)
├── mmc.c # mmc命令(MMC/SD卡操作)
├── tftp.c # tftp命令(网络下载)
├── fatload.c # fatload命令(FAT文件系统读取)
├── sf.c # sf命令(SPI Flash操作)
└── ...
每个命令文件的结构都很清晰:一个do_xxx()函数实现命令逻辑,一个U_BOOT_CMD宏注册命令。想加新命令?复制一个现有文件改改就行。
我的经验:在OTA升级方案中,我经常需要自定义升级命令。比如ota_update命令,就是从cmd/tftp.c和cmd/sf.c里各抄一段,再组合成自己的升级流程。U-Boot的命令框架非常灵活,别怕改。
3.6 drivers/目录:设备驱动的海洋
drivers目录是U-Boot里最大的目录,没有之一。它按设备类型分了子目录:
| 子目录 | 包含的驱动 | OTA升级中的角色 |
|---|---|---|
drivers/net/ |
网卡驱动(e1000、dwmac、sunxi_emac等) | 网络升级的基石 |
drivers/mmc/ |
MMC/SD卡驱动 | 本地升级的存储介质 |
drivers/mtd/ |
NAND/SPI Flash驱动 | 固件存储和烧写 |
drivers/serial/ |
串口驱动 | 调试输出 |
drivers/usb/ |
USB主机/设备驱动 | U盘升级 |
你想想看,OTA升级的本质是什么?就是从某个介质(网络/Flash/U盘)读取新固件,再写入另一个介质(Flash/MMC)。所以drivers目录里的网络驱动、存储驱动,就是OTA升级的「手脚」。手脚不灵,升级必败。
3.7 其他关键目录
还有几个目录,虽然不常改,但必须知道:
- include/:所有头文件的大本营。
include/configs/下是板级配置文件,include/linux/下是内核兼容头文件。 - lib/:通用库函数。比如
lib/crc32.c用于校验,lib/sha256.c用于哈希,OTA升级中校验固件完整性时经常用到。 - net/:网络协议栈。TFTP客户端、DHCP客户端都在这里。网络升级时,这里就是数据流的管道。
- fs/:文件系统支持。从FAT32读升级包,从UBIFS读内核,都靠这里。
- tools/:主机端工具。
mkimage用于制作U-Boot镜像,fdtget/fdtput用于操作设备树。
3.8 知识结构总览
说了这么多,我用一张图把U-Boot的源码结构串起来。这张图我画了很多次,每次给团队新人讲U-Boot时都会用到。
这张图里,我特意标出了OTA升级重点关注目录。你看,drivers/net/负责下载,drivers/mtd/负责烧写,cmd/负责命令调度。这三个目录串起来,就是一条完整的OTA升级链路。
3.9 总结:怎么用好这个结构
最后,我分享几个实战中的小技巧:
- 定位问题先看common/:启动卡住、命令不响应,十有八九是common/里的逻辑出了问题。
- 适配新板子先看board/和arch/:CPU相关去arch/,板级相关去board/,别搞混。
- 加新功能先看cmd/和drivers/:命令在cmd/里加,驱动在drivers/里加,common/尽量不动。
- OTA升级先看net/和drivers/mtd/:网络协议和Flash驱动是升级的命脉。
一个血的教训:我曾经在某个项目里,为了图方便,把板级初始化代码直接写到了common/board_r.c里。结果换了新板子后,代码冲突得一塌糊涂。从那以后,我再也不敢跨目录乱放代码了。U-Boot的目录结构不是摆设,它是无数开发者积累下来的最佳实践。