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时都会用到。

U-Boot 源码结构总览 U-Boot 顶层目录 arch/ CPU架构代码 board/ 板级适配代码 common/ 核心功能模块 cmd/ 命令实现 drivers/ 设备驱动 include/ 头文件/配置 lib/ 通用库函数 net/ 网络协议栈 arch/arm/ cpu/ lib/ dts/ mach-sunxi/ mach-rockchip/ board/mycompany/ myboard.c lowlevel_init.S Kconfig drivers/ net/ mmc/ mtd/ serial/ usb/ (OTA升级核心) cmd/ bootm.c tftp.c nand.c mmc.c sf.c fatload.c OTA升级重点关注目录 drivers/net/ → 网络下载升级包 drivers/mtd/ → 固件烧写到Flash cmd/ → 自定义升级命令

这张图里,我特意标出了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的目录结构不是摆设,它是无数开发者积累下来的最佳实践。