3. U-Boot配置系统:Kconfig与Makefile的交互、配置流程
这一章,我们来聊聊U-Boot的配置系统。说实话,很多刚接触U-Boot的工程师,第一反应就是执行make xxx_defconfig,然后make,就完事了。但如果你不清楚这两步背后发生了什么,遇到配置冲突、编译失败或者想添加自己的分区逻辑时,就会一头雾水。
我个人习惯把U-Boot的配置系统拆成两个阶段来看:配置阶段和编译阶段。它们通过Kconfig和Makefile这两套机制紧密配合。今天我们就把它彻底讲透。
3.1 配置系统的整体架构
先看一张整体流程图,帮你建立全局认知。
这张图把整个流程分成了两个阶段。左边是配置阶段,右边是编译阶段。中间那个.config文件,就是两个阶段的桥梁。
3.2 Kconfig:配置选项的定义与依赖
Kconfig是什么?说白了,它是一套配置选项的描述语言。U-Boot里几乎每个模块都有自己的Kconfig文件,它们按层级组织在一起。
举个例子,你想知道某个驱动能不能被选中,就得看它的Kconfig怎么写。
# drivers/mmc/Kconfig 中的一段
config MMC_SUNXI
bool "Allwinner Sunxi MMC Controller"
depends on ARCH_SUNXI
help
This selects the Allwinner Sunxi MMC controller.
If you have a board based on Allwinner SoC, say Y here.
这里有几个关键点:
- config MMC_SUNXI:定义了一个配置选项,最终会变成
CONFIG_MMC_SUNXI - bool:表示这是一个开关选项(Y/N)
- depends on ARCH_SUNXI:只有选择了ARCH_SUNXI,这个选项才会出现
- help:帮助信息,告诉你这个选项是干嘛的
depends on。我曾经在一个项目里,给一个只支持ARMv8的驱动配了个ARMv7的板子,结果编译出来的镜像直接跑飞。从那以后,我每次加新选项都会先检查依赖链。
3.3 defconfig:板级默认配置
每个开发板都有一个对应的defconfig文件。它存放在configs/目录下,命名规则通常是<board_name>_defconfig。
比如,对于树莓派3:
# configs/rpi_3_defconfig
CONFIG_ARM=y
CONFIG_ARCH_BCM283X=y
CONFIG_TARGET_RPI_3=y
CONFIG_SYS_TEXT_BASE=0x00008000
CONFIG_MMC=y
CONFIG_MMC_SDHCI=y
CONFIG_MMC_SDHCI_BCM2835=y
# 其他选项...
这个文件只包含非默认值的配置项。也就是说,如果某个选项在Kconfig里默认是n,而你的板子需要它,你就在defconfig里写CONFIG_XXX=y。
3.4 make xxx_defconfig 的执行过程
当你敲下make rpi_3_defconfig时,到底发生了什么?我们来拆解一下。
- Makefile 解析目标:顶层Makefile发现目标是以
_defconfig结尾的,就会调用scripts/kconfig/conf工具 - conf 工具读取 Kconfig 树:从
arch/arm/Kconfig开始,递归读取所有依赖的Kconfig文件,构建完整的配置选项树 - 加载 defconfig 文件:把
configs/rpi_3_defconfig里的选项应用到配置树上 - 处理依赖关系:自动选中那些被依赖的选项。比如你选了
MMC_SUNXI,而它依赖ARCH_SUNXI,那么ARCH_SUNXI会被自动设为y - 生成 .config 文件:把最终结果写入
.config,这个文件包含了所有选项的最终值
这个过程可以用一个表格来总结:
| 步骤 | 输入 | 处理工具 | 输出 |
|---|---|---|---|
| 1. 解析目标 | Makefile 目标 | 顶层 Makefile | 调用 conf 工具 |
| 2. 构建配置树 | Kconfig 文件树 | conf | 内存中的配置树 |
| 3. 加载默认值 | defconfig 文件 | conf | 覆盖默认选项 |
| 4. 依赖解析 | 配置树 + 依赖规则 | conf | 自动补全依赖项 |
| 5. 输出配置 | 最终配置树 | conf | .config 文件 |
3.5 从 .config 到编译:Makefile 如何读取配置
.config文件生成后,下一步就是make。但Makefile不能直接读取.config,它需要经过一次预处理。
在顶层Makefile中,你会看到这样一段逻辑:
# 将 .config 转换为 include/config/auto.conf
include/config/auto.conf: .config
$(Q)scripts/kconfig/conf --silentoldconfig $(Kconfig)
这个auto.conf文件才是Makefile真正引用的配置头文件。它的格式和.config几乎一样,但去掉了注释和空行,方便Makefile解析。
然后,Makefile通过include指令把它加载进来:
# 在顶层 Makefile 中
-include include/config/auto.conf
之后,所有的CONFIG_*变量就变成了Makefile的变量。比如:
# 条件编译示例
ifdef CONFIG_MMC
obj-y += drivers/mmc/
endif
如果CONFIG_MMC=y,那么drivers/mmc/目录下的所有文件都会被编译。
make 来更新 auto.conf,否则修改不会生效。
3.6 配置系统的常见坑与避坑指南
做嵌入式这么多年,我在配置系统上踩过的坑还真不少。挑几个典型的说说。
我曾经在一个项目里,添加了一个新的驱动,Kconfig里写了
depends on MMC,但忘了检查MMC本身是否被选中。结果编译时,驱动代码引用了MMC的API,但MMC模块根本没编译进去。链接器报了一堆未定义引用。解决方法: 执行
make menuconfig,按/键搜索你的选项,查看它的依赖链是否完整。
有时候,Kconfig里某个选项默认是
y,但你的板子不需要它。你可能会在defconfig里写# CONFIG_XXX is not set来取消它。但如果你写错了格式,比如漏了#或者空格不对,conf工具会忽略这一行,导致选项仍然是y。解决方法: 执行完
make xxx_defconfig后,立刻检查.config里对应的选项值是否正确。
make xxx_defconfig && make savedefconfig。这个savedefconfig目标会生成一个精简版的defconfig,只包含非默认值的选项。然后我用它替换原来的defconfig文件,确保文件干净整洁。
3.7 实战:添加一个自定义配置选项
光说不练假把式。我们动手添加一个自定义配置选项,看看整个流程怎么走。
假设我们要给一块基于Allwinner H3的板子添加一个LED驱动开关:
- 在Kconfig中添加选项:找到
drivers/led/Kconfig,添加:config LED_H3 bool "H3 LED driver" depends on ARCH_SUNXI help LED driver for Allwinner H3 boards. Say Y here to enable the H3 on-board LED. - 在defconfig中启用它:编辑
configs/sunxi_h3_defconfig,添加一行:CONFIG_LED_H3=y - 在Makefile中添加编译规则:在
drivers/led/Makefile中:obj-$(CONFIG_LED_H3) += led_h3.o - 执行配置和编译:
make sunxi_h3_defconfig make
这样,led_h3.c只有在CONFIG_LED_H3=y时才会被编译。如果你在defconfig里把它设为n,或者干脆不写,它就不会被编译。
3.8 小结
U-Boot的配置系统,说白了就是Kconfig定义选项,defconfig提供板级默认值,Makefile根据配置条件编译。这三者配合得天衣无缝。