2. U-Boot源码结构:顶层目录、板级目录、驱动目录、通用代码目录解析
拿到一份U-Boot源码,第一眼看到几十个文件夹,很多人会懵。我刚开始接触U-Boot时也是这样,对着目录列表发呆——到底该从哪里看起?
其实U-Boot的目录结构设计得很清晰。它遵循一个核心原则:把硬件相关的代码和硬件无关的代码分开。你想想看,U-Boot要支持几百种开发板,如果所有代码都混在一起,那维护成本得多高?
下面这张图能帮你快速建立全局认知:
2.1 顶层目录——从根上理解U-Boot
U-Boot根目录下大概有30多个子目录和文件。别被数量吓到,真正核心的就那么几个。我按重要程度给你排个序:
| 目录/文件 | 作用 | 调试时关注度 |
|---|---|---|
| board/ | 板级初始化代码,每个开发板一个子目录 | ⭐⭐⭐⭐⭐ |
| drivers/ | 外设驱动,MMC、网络、串口等 | ⭐⭐⭐⭐ |
| common/ | 命令处理、控制台、环境变量 | ⭐⭐⭐⭐ |
| arch/ | CPU架构相关,ARM/RISC-V/x86 | ⭐⭐⭐ |
| include/ | 头文件,特别是configs/下的板级配置 | ⭐⭐⭐⭐⭐ |
| net/ | 网络协议栈,TFTP/NFS核心 | ⭐⭐⭐ |
| fs/ | 文件系统支持,ext4/fat等 | ⭐⭐ |
| lib/ | 通用库函数,字符串、加密等 | ⭐⭐ |
| tools/ | 主机端工具,mkimage等 | ⭐ |
board/ 和 include/configs/ 这两个目录。前者告诉我硬件怎么初始化,后者告诉我功能怎么配置。这两个搞清楚了,80%的问题就能定位。
2.2 板级目录 board/——硬件差异的集中营
board/ 目录下按厂商分类,每个厂商下面再按芯片型号分。比如:
board/
├── ti/
│ ├── am335x/ # BeagleBone Black 等
│ ├── omap5/ # OMAP5 系列
│ └── k2hk/ # Keystone II
├── freescale/
│ ├── imx6/ # i.MX6 系列
│ ├── imx8/ # i.MX8 系列
│ └── ls1046a/ # Layerscape
├── samsung/
│ ├── exynos/ # Exynos 系列
│ └── s5p/ # S5P 系列
└── rockchip/
├── rk3288/ # RK3288
└── rk3399/ # RK3399
每个板级目录里通常包含这些文件:
board.c:板级初始化入口,board_init_f()和board_init_r()就在这里Kconfig:板级配置菜单MAINTAINERS:维护者信息,出bug了知道找谁Makefile:编译规则
2.3 驱动目录 drivers/——外设的抽象层
drivers/ 目录按设备类型分类,每个子目录管理一类外设。U-Boot的驱动模型(Driver Model)让这部分代码变得很规整:
drivers/
├── mmc/ # SD卡、eMMC驱动
│ ├── mmc.c # 通用MMC协议层
│ ├── omap_hsmmc.c # TI平台MMC
│ └── fsl_esdhc.c # NXP平台MMC
├── net/ # 以太网驱动
│ ├── eth.c # 通用网络接口
│ ├── fec_mxc.c # NXP FEC控制器
│ └── designware.c # Synopsys DW MAC
├── spi/ # SPI总线驱动
├── i2c/ # I2C总线驱动
├── serial/ # 串口驱动(调试输出关键)
├── gpio/ # GPIO驱动
├── usb/ # USB主机/设备驱动
└── mtd/ # NAND/SPI Flash驱动
驱动目录的调试要点:
- 串口驱动——如果串口没输出,你连U-Boot启动了没都不知道。我建议先确认串口驱动是否正常
- MMC驱动——内核镜像通常存在SD卡或eMMC里,MMC驱动挂了,内核就加载不了
- 网络驱动——TFTP下载内核时,网卡驱动必须工作
驱动调试口诀:
串口先通看输出,MMC再通读内核。
网络最后传文件,逐层验证不出错。
2.4 通用代码目录 common/——U-Boot的大脑
common/ 目录存放的是与硬件无关的通用逻辑。这里面的代码,你在任何板子上都能看到:
| 文件 | 功能 | 调试价值 |
|---|---|---|
main.c |
U-Boot主循环,命令解析入口 | ⭐⭐⭐⭐⭐ |
command.c |
命令注册与查找 | ⭐⭐⭐⭐ |
console.c |
控制台输入输出 | ⭐⭐⭐⭐ |
env_common.c |
环境变量管理 | ⭐⭐⭐ |
bootm.c |
内核启动核心逻辑 | ⭐⭐⭐⭐⭐ |
autoboot.c |
自动启动逻辑 | ⭐⭐⭐ |
这里我想特别说说 main.c。U-Boot启动后,最终会进入 main_loop() 函数。这个函数就做两件事:
- 检查是否满足自动启动条件(比如倒计时内没按键)
- 进入命令行,等待用户输入
你看,整个U-Boot的交互逻辑都浓缩在这个文件里。调试启动流程时,我经常在 main_loop() 里加打印,看看到底是卡在自动启动还是命令处理上。
2.5 其他重要目录
除了上面三个核心目录,还有几个目录也值得了解:
arch/:CPU架构相关代码。ARM、RISC-V、x86等各占一个子目录。这里面有中断向量表、MMU初始化、异常处理等底层代码。调试内核启动时,arch/arm/lib/bootm.c里的do_bootm_linux()是重点include/configs/:板级配置头文件。每个板子对应一个.h文件,比如am335x_evm.h。这里面定义了内存大小、外设地址、环境变量默认值等。我调试时第一个看的就是这个文件net/:网络协议栈。TFTP、NFS、DHCP等协议都在这里。网络启动内核时,这里出问题的概率很高
1.
include/configs/xxx.h —— 配置对不对2.
board/xxx/board.c —— 初始化流程对不对3.
common/bootm.c —— 内核启动参数对不对4.
drivers/xxx/ —— 外设驱动对不对这个顺序能覆盖90%的问题。
2.6 目录结构背后的设计哲学
U-Boot的目录结构,说白了就是分层隔离。你想想看:
- 板级差异 →
board/目录 - CPU架构差异 →
arch/目录 - 外设差异 →
drivers/目录 - 通用逻辑 →
common/目录
这种设计的好处很明显:你要移植U-Boot到新板子,只需要改 board/ 和 include/configs/ 下的文件,最多再改改 drivers/ 里对应的外设驱动。通用代码基本不用动。
我记得有一次帮客户调试,他们的板子用的是定制的FPGA,挂了一个奇怪的网络控制器。我只需要在 drivers/net/ 下加一个新驱动文件,然后在 board/ 目录里注册一下,其他代码完全不用改。这就是分层设计的力量。
好了,目录结构就讲到这里。记住一个原则:硬件相关的去 board/ 和 drivers/ 找,逻辑相关的去 common/ 找,配置相关的去 include/configs/ 找。按这个思路,你很快就能在U-Boot源码里找到你想要的东西。