3. U Boot 源码结构与目录解析
U-Boot的配置系统,说白了就是一套「搭积木」的规则。你告诉它要什么功能,它帮你把对应的代码编译进去。我刚开始接触U-Boot时,总觉得这玩意儿很玄乎,不就是make menuconfig吗?后来自己移植一块板子,才发现这里面的门道深着呢。
今天咱们就把Kconfig、Makefile、defconfig这三兄弟掰开揉碎,看看它们到底是怎么配合的。
3.1 三者的角色分工
先给个总览。这三样东西,各司其职:
| 组件 | 角色 | 类比 |
|---|---|---|
| Kconfig | 定义配置选项的「菜单」 | 饭店的菜单,列出所有可点的菜 |
| defconfig | 保存某块板子的「默认点单」 | 老顾客的固定套餐 |
| Makefile | 根据点单结果「做菜」 | 后厨的烹饪流程 |
你执行 make xxx_defconfig,就是把「固定套餐」加载进来。然后 make menuconfig 让你微调。最后 make 命令根据最终配置去编译。嗯,流程就是这么简单。
核心流程:
defconfig → .config → Makefile 读取 → 条件编译 → 生成 u-boot.bin
3.2 Kconfig:配置选项的「宪法」
Kconfig 文件定义了所有可配置的选项。它分布在 U-Boot 源码的各个目录下,顶层有个 Kconfig,子目录也有自己的 Kconfig,通过 source 语句层层包含。
举个例子,你想给板子加个网络功能,Kconfig 里大概是这么写的:
config CMD_NET
bool "Enable networking commands"
default y
help
This enables basic networking commands like ping, tftp, etc.
这里 bool 表示开关选项,要么 y 要么 n。default y 表示默认开启。我个人习惯把常用功能默认打开,省得每次移植都要手动选一遍。
Kconfig 还支持 depends on 和 select。比如:
config CMD_TFTP
bool "tftp command"
depends on CMD_NET
意思是只有网络功能开启了,tftp 命令才会出现。这种依赖关系,我在项目中遇到过好几次——有人把 CMD_NET 关了,然后跑来问我为什么 tftp 编译不过。你想想看,菜单里都没这道菜,后厨怎么做?
小技巧: 用 make menuconfig 时,按 / 键可以搜索选项名。快速定位,不用翻菜单翻到眼花。
3.3 defconfig:板级的「身份证」
每个板子都有自己的 defconfig 文件,放在 configs/ 目录下。命名规则一般是 <板子名>_defconfig。
打开一个典型的 defconfig,内容长这样:
CONFIG_ARM=y
CONFIG_TARGET_MYBOARD=y
CONFIG_SYS_TEXT_BASE=0x80000000
CONFIG_CMD_NET=y
CONFIG_CMD_PING=y
# CONFIG_CMD_LOADS is not set
注意看,这里只写了「和默认值不一样」的选项。比如 CONFIG_CMD_NET=y 是显式开启,而 # CONFIG_CMD_LOADS is not set 是显式关闭。那些没出现的选项,就用 Kconfig 里定义的默认值。
我曾经犯过一个错:在 defconfig 里写了一大堆选项,以为越多越保险。结果升级 U-Boot 版本时,Kconfig 里有些选项改名了,我的 defconfig 直接报错。后来我学乖了——只写必要的,其他的交给默认值。
避坑指南: 不要手动编辑 .config 文件!它是由 defconfig 和 menuconfig 自动生成的。手动改 .config 后,下次 make menuconfig 会被覆盖。要改配置,要么改 defconfig,要么用 menuconfig。
3.4 Makefile:配置的「执行者」
配置选好了,怎么让代码知道该编译哪些文件?答案在 Makefile 里。
U-Boot 的 Makefile 大量使用条件判断:
# 在 board/myboard/Makefile 中
obj-y += myboard.o
obj-$(CONFIG_CMD_NET) += eth.o
obj-$(CONFIG_CMD_USB) += usb.o
这里 obj-y 表示无论如何都编译,obj-$(CONFIG_CMD_NET) 表示只有 CONFIG_CMD_NET=y 时才编译 eth.o。如果 CONFIG_CMD_NET 没定义或者为 n,那这一行就变成了 obj-,啥也不编译。
说白了,Makefile 就是个翻译官——把 .config 里的 y/n 翻译成「编译」或「不编译」。
还有更高级的用法,比如根据配置选择不同的驱动:
ifdef CONFIG_MYBOARD_V1
obj-y += drv_v1.o
else
obj-y += drv_v2.o
endif
这种写法在板级支持包里很常见。同一套代码,通过配置切换驱动版本,省得维护两套源码。
3.5 配置生成流程全景图
咱们把整个流程串起来,画张图看看:
从这张图能看得很清楚:Kconfig 是「菜单定义」,defconfig 是「默认点单」,menuconfig 是「临时加菜」,最终合并成 .config。然后 .config 生成 autoconf.h,Makefile 根据这些宏决定编译哪些源文件。
3.6 实战:从零配置一块新板子
假设你要给一块新板子添加支持,步骤是这样的:
- 创建 Kconfig:在
board/你的板子/Kconfig里定义板级选项,比如选择 SoC 型号、DDR 大小等。 - 创建 defconfig:在
configs/你的板子_defconfig里写下必要的配置项。 - 执行配置:
make 你的板子_defconfig,生成 .config。 - 微调:
make menuconfig,检查有没有遗漏的功能。 - 编译:
make,看能不能一次通过。
我记得第一次移植时,第三步就卡住了——make xxx_defconfig 报错说找不到规则。后来发现是 defconfig 文件名写错了,少了个下划线。这种低级错误,犯过一次就记住了。
调试技巧: 如果编译报错说某个宏未定义,去 include/autoconf.h 里搜一下,看这个宏到底有没有生成。如果没有,说明配置没选上,回 Kconfig 或 defconfig 里找原因。
3.7 常见问题与避坑
最后聊几个我踩过的坑:
- defconfig 里写错了选项名:不会报错,但选项不生效。因为 Kconfig 里没这个选项,它就被忽略了。我曾经把
CONFIG_CMD_PING拼成CONFIG_CMD_PINGG,查了半天才发现。 - Kconfig 依赖关系没理清:比如你选了
CMD_TFTP,但没选CMD_NET,menuconfig 里根本看不到 tftp 选项。这时候要去检查depends on链。 - 默认值冲突:Kconfig 里
default y,defconfig 里又写CONFIG_xxx=y,虽然结果一样,但显得冗余。我建议 defconfig 只写和默认值不同的。
嗯,配置系统这块,说白了就是「定义-选择-编译」三个环节。Kconfig 定义菜单,defconfig 帮你点好菜,Makefile 负责做菜。搞懂了这三者的关系,U-Boot 移植就算入门了。