2. U-Boot源码结构:顶层目录结构解析、关键子目录功能、Makefile与Kconfig构建系统
拿到一份U-Boot源码,第一眼看到几十个目录,别慌。我刚开始接触U-Boot时也懵过——这玩意儿比Linux内核小多了,但目录结构设计得相当精巧。说白了,U-Boot的源码组织遵循一个核心原则:平台无关代码与平台相关代码分离。搞懂这个,你就抓住了它的命脉。
2.1 顶层目录结构一览
先看顶层目录。我习惯用 tree -L 1 扫一眼,大概长这样:
u-boot/
├── arch/ # 架构相关代码(ARM、RISC-V、x86等)
├── board/ # 板级相关代码(你的板子在这里)
├── common/ # 通用功能模块(命令、环境变量等)
├── configs/ # 默认配置文件(defconfig)
├── doc/ # 文档
├── drivers/ # 设备驱动(串口、网卡、存储等)
├── dts/ # 设备树源文件
├── fs/ # 文件系统支持
├── include/ # 头文件
├── lib/ # 通用库函数
├── net/ # 网络协议栈
├── scripts/ # 构建脚本
├── tools/ # 主机端工具
├── Makefile # 顶层Makefile
└── Kconfig # 顶层Kconfig配置入口
嗯,这里要注意:arch/ 和 board/ 是移植工作的主战场。其他目录虽然也重要,但大部分时候你不需要动它们。
2.2 关键子目录功能详解
arch/ —— 架构核心
这个目录按CPU架构分,比如 arch/arm/、arch/riscv/、arch/x86/。每个架构下又细分:
cpu/—— CPU初始化、异常处理、缓存操作lib/—— 架构相关的库函数(如 memcpy 优化版)include/—— 架构相关的头文件mach-xxx/—— 具体SoC厂商的代码(如 mach-sunxi、mach-imx)
我在移植时,90%的汇编级调试都耗在 arch/arm/cpu/ 里。尤其是 lowlevel_init.S 和 start.S,那是U-Boot的第一口呼吸。
board/ —— 你的板子
每个板子一个目录,比如 board/ti/am335x/。里面通常包含:
board.c—— 板级初始化函数(board_init、dram_init等)Kconfig—— 板级配置选项MAINTAINERS—— 维护者信息Makefile—— 编译规则
board/ 下最接近的参考板,复制一份改。别从头写,那是给自己找罪受。
drivers/ —— 驱动大本营
U-Boot的驱动模型(Driver Model, DM)把所有驱动按类别放:
| 子目录 | 功能 | 常见文件 |
|---|---|---|
| serial/ | 串口驱动 | ns16550.c, pl011.c |
| net/ | 网卡驱动 | designware.c, fec_mxc.c |
| mmc/ | SD/MMC/eMMC驱动 | sdhci.c, sunxi_mmc.c |
| spi/ | SPI控制器驱动 | spi-sunxi.c, spi-imx.c |
| i2c/ | I2C控制器驱动 | i2c-mux.c, mvtwsi.c |
| gpio/ | GPIO驱动 | gpio-sunxi.c, gpio-imx.c |
我曾经在移植某款全志芯片时,串口死活不出数据。折腾了两天,最后发现是 drivers/serial/ 里的时钟配置宏没打开。嗯,从那以后我养成了先查驱动Kconfig的习惯。
common/ —— 通用功能
这里放的是U-Boot的“内脏”:
main.c—— 主循环(命令解析、执行)command.c—— 命令注册与查找env_common.c—— 环境变量操作console.c—— 控制台输入输出board_r.c—— 重定位后的板级初始化流程
你想想看,U-Boot启动第二阶段(重定位到RAM后)的初始化顺序,就在 board_r.c 的 init_sequence_r[] 数组里。想加自己的初始化函数?往这个数组里插就行。
其他重要目录
- configs/ —— 板级默认配置,
make xxx_defconfig就靠它 - dts/ —— 设备树源文件,现代U-Boot越来越依赖设备树
- include/configs/ —— 板级头文件配置(老式配置方式,逐渐被Kconfig取代)
- lib/ —— zlib、crc32、sha256等通用算法
- net/ —— TFTP、NFS、DHCP等网络协议实现
2.3 Makefile与Kconfig构建系统
U-Boot的构建系统是从Linux内核学来的,Kconfig + Makefile 的组合。说白了,就是“配置-编译”两步走。
Kconfig:配置你的U-Boot
Kconfig文件定义了所有可配置选项,顶层 Kconfig 通过 source 语句层层包含:
# 顶层 Kconfig
mainmenu "U-Boot Configuration"
source "arch/Kconfig"
source "board/Kconfig"
source "drivers/Kconfig"
source "fs/Kconfig"
source "lib/Kconfig"
...
每个子目录下的Kconfig定义自己的选项。比如 drivers/serial/Kconfig:
config DM_SERIAL
bool "Enable Driver Model for UART drivers"
help
Enable driver model for serial/UART drivers.
启用驱动模型下的串口驱动。
config SYS_NS16550
bool "NS16550 UART support"
depends on DM_SERIAL
help
Support for NS16550-compatible UARTs.
配置时执行 make menuconfig,就会生成 .config 文件。这个文件会被顶层Makefile读取,决定编译哪些文件、定义哪些宏。
make menuconfig 里改了串口波特率,但忘了保存。结果烧进去还是115200,排查了半天。记住:menuconfig 改完后一定要选 Save,或者直接 make savedefconfig。
Makefile:编译你的U-Boot
顶层Makefile是整个构建的入口。它的工作流程大致是:
- 读取
.config,确定要编译哪些功能 - 递归进入各子目录,执行子Makefile
- 链接所有目标文件,生成
u-boot可执行文件 - 处理成最终烧录格式(如
u-boot.bin、u-boot.img)
子目录的Makefile写法很统一,比如 drivers/serial/Makefile:
obj-$(CONFIG_SYS_NS16550) += ns16550.o
obj-$(CONFIG_PL011_SERIAL) += pl011.o
obj-$(CONFIG_SUNXI_SERIAL) += sunxi_serial.o
这里的 obj-$(CONFIG_XXX) 是条件编译。如果 .config 里定义了 CONFIG_SYS_NS16550=y,就编译 ns16550.c。没定义?跳过。
我个人觉得,理解了这个机制,移植时加驱动就很简单了:把驱动文件放到对应目录,在Kconfig里加选项,在Makefile里加编译规则,完事。
2.4 知识结构图:U-Boot源码组织与构建流程
下面这张图把U-Boot的目录层级、关键子目录职责、以及从配置到编译的流程串起来了。建议你保存下来,移植时对照着看。
2.5 移植时最常用的目录速查表
最后,我整理了一张速查表。移植时遇到问题,先看对应目录,能省不少时间。
| 移植任务 | 主要目录 | 关键文件 |
|---|---|---|
| 添加新板子 | board/、configs/、include/configs/ | board.c、xxx_defconfig、xxx.h |
| 修改串口驱动 | drivers/serial/、arch/arm/dts/ | ns16550.c、xxx.dts |
| 调整DDR参数 | board/xxx/、arch/arm/mach-xxx/ | dram.c、dram_init() |
| 添加新命令 | common/、cmd/ | command.c、cmd/xxx.c |
| 修改启动流程 | common/、arch/arm/lib/ | board_r.c、board_f.c |
| 配置裁剪 | Kconfig(各目录)、configs/ | xxx_defconfig、.config |