4. U-Boot 配置体系:Kconfig、defconfig 与 menuconfig
U-Boot 的配置体系,说白了就是三样东西:Kconfig 定义选项、defconfig 保存默认值、menuconfig 让你可视化修改。这三者配合起来,才构成了一套完整的配置管理流程。
我刚开始接触 U-Boot 移植时,最头疼的就是配置系统。那时候还是老式的 mkconfig 脚本,改个配置要手动编辑一堆头文件。后来 Kconfig 全面引入,整个体验好了不止一个档次。你想想看,一个支持几百种开发板的 Bootloader,如果没有一套清晰的配置体系,那维护起来简直是噩梦。
4.1 Kconfig 配置系统:选项的定义与依赖
Kconfig 语法源自 Linux 内核,U-Boot 直接继承了过来。它的作用就是定义"有哪些配置选项",以及这些选项之间的依赖关系。
每个选项都是一个 config 条目,放在各个目录的 Kconfig 文件中。顶层 Kconfig 通过 source 语句逐级包含子目录的 Kconfig,形成一棵完整的配置树。
# 顶层 Kconfig 示例 (arch/arm/Kconfig)
source "arch/arm/mach-sunxi/Kconfig"
source "arch/arm/mach-rockchip/Kconfig"
source "arch/arm/mach-imx/Kconfig"
# 某个 SoC 家族的 Kconfig 片段
config ARCH_SUNXI
bool "Support Allwinner SUNXI SoCs"
select CPU_V7A
select SPL
help
Allwinner sunxi SoCs, used in many tablets and single-board computers.
这里有个关键点:select 和 depends on。前者是"主动选中",后者是"被动依赖"。我在项目中遇到过好几次因为 select 链太长导致配置冲突的情况,后来养成了一个习惯——能用 depends on 就别用 select,除非你非常确定不会产生循环依赖。
| 关键字 | 含义 | 典型用法 |
|---|---|---|
bool |
开关选项(y/n) | 是否启用某个功能 |
tristate |
三态(y/m/n) | U-Boot 中很少用,内核常见 |
int |
整数输入 | 时钟频率、内存地址 |
hex |
十六进制输入 | 寄存器基地址 |
string |
字符串输入 | 默认命令行参数 |
depends on |
依赖条件 | 只有 A 选中时 B 才可见 |
select |
强制选中 | 选中 A 时自动选中 B |
imply |
建议选中 | 选中 A 时推荐选中 B,但可手动取消 |
config TARGET_MY_BOARD,然后通过 select 把 SoC 级和架构级的选项链起来。这样 menuconfig 里就能看到新板子了。
4.2 defconfig 文件解析:板级的默认配置
defconfig 文件放在 configs/ 目录下,命名规则通常是 <板子名>_defconfig。它保存的是这个板子的"最小配置集"——只记录与默认值不同的选项。
嗯,这里要注意:defconfig 不是完整的 .config 文件。它只包含你显式设置的选项,其他选项在编译时由 Kconfig 的默认值填充。这样做的好处是文件短小精悍,便于维护。
# configs/sunxi_defconfig 示例
CONFIG_ARM=y
CONFIG_ARCH_SUNXI=y
CONFIG_MACH_SUN8I=y
CONFIG_DRAM_CLK=672
CONFIG_DRAM_ZQ=123
CONFIG_MMC=y
CONFIG_MMC_SUNXI=y
CONFIG_USB=y
CONFIG_USB_EHCI_HCD=y
# CONFIG_VIDEO is not set
看到 # CONFIG_VIDEO is not set 这种写法了吗?它表示显式关闭某个选项。如果不写这一行,Kconfig 会使用该选项的默认值——可能是 y,也可能是 n。所以 defconfig 里只记录"与默认值不同"的选项。
我曾经踩过一个坑:移植一块新板子时,我复制了另一块板子的 defconfig,没注意到某个外设的默认值是 y,而我的板子没有这个外设。结果编译出来的 U-Boot 在初始化那个外设时直接卡死。排查了半天才发现是 defconfig 里少了一行 # CONFIG_XXX is not set。
make savedefconfig。它会自动生成一个只包含差异项的 defconfig,比手动编辑靠谱得多。
4.3 menuconfig 图形化配置:交互式配置工具
menuconfig 是基于 ncurses 的终端图形界面。执行 make menuconfig 后,你会看到一个分层的配置菜单,可以用方向键浏览、空格键切换选项。
它的底层逻辑很简单:读取 Kconfig 树生成菜单结构,读取 .config 文件填充当前值,修改后写回 .config。说白了就是一个"可视化编辑器"。
# 常用操作流程
make menuconfig # 进入图形界面
# 修改配置后保存退出
make # 编译 U-Boot
我个人习惯在 menuconfig 里按 / 键搜索选项。比如我想找 CONFIG_BOOTDELAY,直接按 / 输入 bootdelay,它会列出所有匹配项及其位置路径。这个功能在配置项上千的时候特别有用。
menuconfig 里每个选项有三种状态:
- [*] — 编译进内核(y)
- [ ] — 不编译(n)
- <M> — 编译为模块(m),U-Boot 中极少用
按 ? 键可以查看选项的帮助信息,包括它的依赖关系和被哪些选项 select。我在排查配置冲突时经常用这个功能。
diff .config.old .config。menuconfig 会自动备份旧配置到 .config.old。
4.4 三者的协作流程
理解了每个部分的作用,我们来看看它们是怎么配合的。下面这张图展示了从 defconfig 到最终编译的完整流程:
流程其实不复杂:
- make defconfig — 读取 Kconfig 树,加载 defconfig 中的差异项,生成初始 .config
- make menuconfig(可选)— 在 .config 基础上做微调
- make — 根据 .config 编译 U-Boot
- make savedefconfig(可选)— 从 .config 反向生成精简的 defconfig
make <相近板子>_defconfig,然后 make menuconfig 修改差异项,最后 make savedefconfig 生成自己的 defconfig。这样既快又不容易漏选项。
4.5 配置体系的调试技巧
配置出问题的时候,别急着怀疑代码。先检查配置本身。我常用的三板斧:
- 检查 .config — 直接看
.config文件,确认你想要的选项是不是 y。有时候 menuconfig 里看着选中了,实际因为依赖不满足被自动取消了。 - 查看 Kconfig 依赖链 — 用
make menuconfig进入选项后按?,看它的Depends on和Selects列表。我曾经遇到过因为某个驱动依赖的 DMA 控制器没选,导致整个驱动在 menuconfig 里都看不到。 - 对比 defconfig — 拿你的 defconfig 和官方参考板子的 defconfig 做 diff,往往能发现遗漏的选项。
make config(命令行问答式)才是正道。
嗯,关于配置体系就讲这么多。Kconfig、defconfig、menuconfig 这三者,说白了就是一套"定义-默认-交互"的配置管理框架。理解了这个框架,你就能自如地给新板子添加支持,也能快速定位配置相关的问题。