U-Boot 配置系统:从 Kconfig 到最终镜像的“配方”
做嵌入式开发,尤其是跟 U-Boot 打交道,你迟早要面对一个问题:怎么给这块板子“量身定制”一个 Bootloader?
不同芯片、不同外设、不同内存布局,U-Boot 不可能一个二进制通吃。所以它搞了一套配置系统。说白了,就是让你像点菜一样,勾选需要的功能,生成一份“菜单”,然后编译脚本照着这份菜单去干活。
这套系统核心就四个东西:Kconfig 机制、defconfig 文件、menuconfig 图形界面,以及配置文件的生成过程。今天咱们就把这四块掰开揉碎了讲清楚。
一句话总结:Kconfig 定义“有什么菜”,defconfig 是“默认菜单”,menuconfig 让你“临时改菜单”,最终生成 .config 文件交给编译系统。
3.1 Kconfig 机制:配置项的“宪法”
Kconfig 不是 U-Boot 独有的,Linux 内核也用这套。它定义了一套语法,用来描述每个配置选项是什么类型、依赖什么、默认值是多少。
在 U-Boot 源码里,Kconfig 文件散落在各个目录下。比如 arch/arm/Kconfig 定义 ARM 架构相关的选项,drivers/net/Kconfig 定义网卡驱动选项。它们通过 source 语句层层包含,最终形成一棵完整的配置树。
我刚开始接触时,觉得这玩意儿就是个“选择题库”。后来踩过坑才明白,依赖关系才是 Kconfig 的精髓。
3.1.1 常见配置类型
| 类型 | 关键字 | 说明 | 典型例子 |
|---|---|---|---|
| 布尔型 | bool | 要么选上 (y),要么不选 | CONFIG_CMD_BOOTM |
| 三态型 | tristate | y / m (模块) / n,U-Boot 里很少用 m | CONFIG_USB |
| 整型 | int | 输入一个数字 | CONFIG_SYS_MALLOC_LEN |
| 字符串 | string | 输入一段文本 | CONFIG_BOOTCOMMAND |
| 十六进制 | hex | 输入十六进制数 | CONFIG_SYS_TEXT_BASE |
举个例子,下面这段来自 common/Kconfig:
config CMD_BOOTM
bool "bootm command"
default y
help
Boot an application image from memory.
This is the core command for booting Linux.
意思是:定义一个叫 CONFIG_CMD_BOOTM 的布尔选项,默认选中,功能是启动内存中的镜像。
我的习惯:每次移植新板子,我都会先 grep 一下 default y 的选项,看看哪些功能默认开了。有些你根本用不到,比如网络启动相关,关掉能省不少空间。
3.1.2 依赖与反向依赖
Kconfig 最让我头疼过的是 depends on 和 select。
- depends on:A 依赖于 B,B 没选,A 就不可见或自动变 n。
- select:选了 A,自动把 B 也选上。有点像“强依赖”。
我曾经遇到过一个问题:明明在 menuconfig 里选了某个驱动,编译却报错说某个符号未定义。查了半天,发现那个驱动 select 了一个我没注意到的底层模块,而那个模块又有 depends on 没满足。嗯,从那以后我改配置都会多看两眼依赖链。
3.2 defconfig 文件:板级的“默认配方”
每个官方支持的板子,在 configs/ 目录下都有一个 <board_name>_defconfig 文件。比如 configs/sandbox_defconfig、configs/stm32mp15_defconfig。
这个文件长什么样?说白了就是一行一个 CONFIG_XXX=y 或 CONFIG_XXX=值。它只记录与默认值不同的选项。
举个例子,configs/sandbox_defconfig 里可能有:
CONFIG_SANDBOX=y
CONFIG_SANDBOX_SDL=y
CONFIG_CMD_BOOTM=y
CONFIG_BOOTCOMMAND="run distro_bootcmd"
你想想看,如果所有选项都写进去,这个文件得几千行。所以 defconfig 只写“增量”。编译时,系统会先拿 Kconfig 的默认值,再用 defconfig 里的值覆盖。
避坑指南:千万不要手动去改 .config 文件!我刚开始不懂,直接编辑 .config 加了个选项,结果下次 make menuconfig 一保存,我加的内容全丢了。正确的做法是改 defconfig,或者用 menuconfig 保存。
3.3 menuconfig:图形化的“点菜界面”
在源码根目录执行 make menuconfig,你会看到一个基于 ncurses 的图形界面。上下键移动,空格键切换选中状态,回车进入子菜单。
这个界面本质上是在读写 .config 文件。你每改一个选项,它背后就修改对应的 CONFIG_XXX 值。
我个人习惯是:先用 defconfig 打个底,再用 menuconfig 微调。比如调试时临时打开某个 debug 选项,或者调整内存大小。
menuconfig 有几个实用操作:
- / 键:搜索符号,快速定位选项位置
- Z 键:显示所有选项(包括被依赖隐藏的)
- ? 键:查看选项的帮助信息
我记得有次帮同事定位问题,他死活找不到某个配置项。我过去按了个 /,输入关键字,一秒定位。他当时就愣住了。
3.4 配置文件的生成过程:从 defconfig 到 .config
这个过程是 U-Boot 构建的第一步,也是很多人忽略的一步。咱们来捋一捋。
执行 make xxx_defconfig 时,实际干了三件事:
- 加载 Kconfig 默认值:系统遍历所有 Kconfig 文件,生成一个“全默认”的配置。
- 应用 defconfig 覆盖:读取
configs/xxx_defconfig,把里面指定的选项覆盖上去。 - 生成 .config:把最终结果写入
.config文件。
之后执行 make 编译时,.config 会被转换成 include/autoconf.mk(给 Makefile 用)和 include/generated/autoconf.h(给 C 代码用)。
你看,整个链路就是:Kconfig 定义 → defconfig 预设 → menuconfig 微调 → .config 生成 → 编译使用。
下面这张图能帮你更直观地理解:
3.5 实战:如何快速验证配置是否生效
说了这么多理论,来点实际的。假设你给一块新板子移植 U-Boot,想确认某个配置有没有被正确加载。
我的做法分三步:
- 检查 .config:
grep CONFIG_MY_FEATURE .config,看有没有定义。 - 检查 autoconf.h:
grep CONFIG_MY_FEATURE include/generated/autoconf.h,看 C 代码能不能用。 - 检查编译日志:
make V=1 2>&1 | grep MY_FEATURE,看编译器有没有收到-DCONFIG_MY_FEATURE。
如果第三步没输出,说明配置根本没传给编译器。这时候回头查 Kconfig 的依赖关系,十有八九是 depends on 没满足。
一个小技巧:在 make menuconfig 里,按 / 搜索你的选项,它会直接告诉你这个选项当前是什么状态,以及为什么被隐藏(如果有依赖没满足的话)。这比翻 Kconfig 文件快多了。
嗯,关于 U-Boot 的配置系统,核心就是这些。你只要记住:Kconfig 是规则,defconfig 是默认值,menuconfig 是调整工具,.config 是最终结果。搞清楚了这四者的关系,以后改配置、加功能、移植板子,心里就有底了。