4、U-Boot源码结构:目录、编译框架与配置系统
U-Boot 这玩意儿,说白了就是嵌入式世界的「万能启动器」。你拿到一块新板子,第一件事就是把它跑起来。我这些年经手的项目,从 ARM9 到 Cortex-A72,几乎都在跟它打交道。
这一章,咱们就把它扒开看看。目录怎么组织的?编译怎么串联的?配置系统又是什么鬼?搞懂了这些,你才算真正入了 Bootloader 的门。
4.1 顶层目录结构:一眼看穿 U-Boot
先说说顶层目录。你下载一份 U-Boot 源码,解压后第一眼看到的就是这些文件夹。别慌,我帮你捋一捋。
| 目录/文件 | 作用 | 我的经验 |
|---|---|---|
arch/ |
架构相关代码(ARM、RISC-V、x86 等) | 改板级支持,90% 的时间泡在这里 |
board/ |
具体板级初始化、外设配置 | 每个项目都要新建一个目录 |
configs/ |
默认配置文件(defconfig) | 我习惯从这里开始改配置 |
include/ |
全局头文件、配置宏定义 | configs/ 下的头文件是核心 |
drivers/ |
各种驱动(串口、网卡、MMC、NAND 等) | 移植新硬件,先看这里有没有现成的 |
common/ |
通用命令、核心逻辑 | 比如 cmd/ 下的命令实现 |
lib/ |
通用库函数(CRC、加密、字符串处理) | 基本不动,除非要加新算法 |
tools/ |
主机端工具(mkimage、fdt 处理等) | 打包镜像时经常用到 |
doc/ |
文档、README | 遇到坑,先翻翻这里 |
嗯,这里要注意:arch/ 和 board/ 是移植工作的主战场。我刚开始做 U-Boot 移植时,总喜欢在 common/ 里乱改,后来发现那是给自己挖坑——架构相关的代码,一定要放在对应的 arch/ 目录下。
4.2 编译框架:Kbuild 与 Makefile 的协作
U-Boot 的编译框架,其实是从 Linux 内核学来的。它用了一套叫 Kbuild 的系统。说白了,就是通过 Makefile 把成千上万个 .c 文件组织起来,最终生成一个 u-boot.bin。
4.2.1 顶层 Makefile 做了什么?
你执行 make 时,顶层 Makefile 会做三件事:
- 解析配置:读取
.config文件,决定哪些代码被编译。 - 递归进入子目录:根据配置,逐层进入
arch/、board/、drivers/等目录。 - 链接生成镜像:把所有目标文件链接成
u-boot可执行文件,再转换成u-boot.bin。
举个例子,你想编译一个 ARM 板子的 U-Boot:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- myboard_defconfig
make -j4
第一行是配置,第二行是编译。我个人习惯把 CROSS_COMPILE 写进环境变量,省得每次敲。
4.2.2 Kbuild 的核心:obj-y 与 obj-$(CONFIG_xxx)
每个子目录下都有一个 Makefile,里面写着类似这样的内容:
obj-y += my_driver.o
obj-$(CONFIG_MY_FEATURE) += my_feature.o
这里 obj-y 表示「总是编译」,obj-$(CONFIG_xxx) 表示「如果配置了 xxx 就编译」。你想想看,这其实就是个条件编译的开关。我在项目中遇到过一个问题:明明加了驱动文件,但编译出来就是没有。后来发现是 CONFIG_xxx 没打开,驱动根本没被编译进去。
make V=1 查看详细的编译命令。这样能清楚看到每个文件有没有被编译,链接顺序对不对。
4.3 配置系统:从 defconfig 到 .config
U-Boot 的配置系统,经历过一次大改。早期版本用的是 include/configs/ 下的头文件,现在主流版本(2016 年以后)用的是 Kconfig 加 defconfig 的方式。
4.3.1 配置流程
整个配置过程,其实就三步:
- 选择默认配置:
make xxx_defconfig,从configs/目录下读取默认配置。 - 生成 .config:把默认配置展开,生成一个完整的
.config文件。 - 生成头文件:编译时,
.config会被转换成include/autoconf.mk和include/generated/autoconf.h,供 Makefile 和 C 代码使用。
我曾经踩过一个坑:手动改了 .config 后,直接 make,结果配置没生效。后来才发现,改完 .config 后必须执行 make olddefconfig 来同步依赖关系。
4.3.2 Kconfig 的层级结构
Kconfig 文件分布在各个目录下,形成了一个树状结构。顶层 Kconfig 会 source 子目录的 Kconfig,子目录再 source 更细的模块。
举个例子,你想给板子加一个网络功能,配置路径可能是:
Device Drivers → Network device support → Ethernet driver → My Ethernet driver
对应的 Kconfig 写法:
config MY_ETHERNET
bool "My Ethernet driver"
depends on DM_ETH
help
Say Y here to enable My Ethernet driver.
depends on 和 select 的区别要搞清楚。depends on 是「依赖」,如果父选项没开,子选项不会显示;select 是「自动选中」,常用于强制开启某个依赖。我见过有人用 select 导致循环依赖,编译直接挂掉。
4.4 知识体系总览
说了这么多,咱们用一张图把 U-Boot 源码结构、编译框架和配置系统的关系串起来。这张图我画了好几次,才把逻辑理清楚。
4.5 避坑指南:我踩过的几个坑
最后,分享几个实战中容易踩的坑。这些可都是真金白银换来的教训。
- 配置没生效:改完
.config后,一定要执行make olddefconfig或make savedefconfig。我有一回改了个驱动开关,直接make,结果编译出来还是老样子,排查了半天才发现是配置没同步。 - 链接顺序问题:U-Boot 对链接顺序有要求,特别是初始化函数。如果你在
board/下加了新文件,记得检查Makefile中的obj-y顺序。我曾经因为顺序不对,导致板子启动到一半就挂了。 - 头文件路径:U-Boot 的头文件搜索路径很复杂,有
include/、arch/arm/include/、board/xxx/include/等。如果你遇到「找不到头文件」的错误,先看看-I参数有没有包含你的路径。 - SPL 与 U-Boot 的配置冲突:现在很多板子用 SPL(Secondary Program Loader)来加载 U-Boot。SPL 和 U-Boot 共用一部分配置,但有些配置只能用于 SPL。我见过有人把
CONFIG_SPL_BUILD用错了地方,导致 SPL 编译出来的镜像比 U-Boot 还大。
一句话总结:U-Boot 的源码结构、编译框架和配置系统,是一个三位一体的整体。搞懂了目录怎么组织、Makefile 怎么串联、Kconfig 怎么控制开关,你就能像庖丁解牛一样,轻松驾驭任何板子的 U-Boot 移植工作。