4. U-Boot 配置体系:Kconfig、defconfig 与 menuconfig

U-Boot 的配置体系,说白了就是三样东西:Kconfig 定义选项、defconfig 保存默认值、menuconfig 让你可视化修改。这三者配合起来,才构成了一套完整的配置管理流程。

我刚开始接触 U-Boot 移植时,最头疼的就是配置系统。那时候还是老式的 mkconfig 脚本,改个配置要手动编辑一堆头文件。后来 Kconfig 全面引入,整个体验好了不止一个档次。你想想看,一个支持几百种开发板的 Bootloader,如果没有一套清晰的配置体系,那维护起来简直是噩梦。

核心理解:Kconfig 是配置的"定义层",defconfig 是"默认值层",menuconfig 是"交互层"。三者各司其职,缺一不可。

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,但可手动取消
我的习惯:在添加新板级支持时,我会先在对应 mach 目录的 Kconfig 中加一个 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。

避坑指南:生成 defconfig 的正确姿势是先用 menuconfig 配好,然后执行 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。我在排查配置冲突时经常用这个功能。

效率技巧:如果你在 menuconfig 里改了很多选项,想看看改了哪些,可以退出后执行 diff .config.old .config。menuconfig 会自动备份旧配置到 .config.old。

4.4 三者的协作流程

理解了每个部分的作用,我们来看看它们是怎么配合的。下面这张图展示了从 defconfig 到最终编译的完整流程:

U-Boot 配置体系协作流程 Kconfig 定义 选项、依赖、默认值 defconfig 默认值 板级差异配置 menuconfig 交互 可视化修改 读取 加载 make 自动合并生成 .config Kconfig 默认值 + defconfig 覆盖值 = 完整配置 编译生成 u-boot.bin savedefconfig 回写 💡 关键流程: make defconfig → 读取 Kconfig 树 → 加载 defconfig 差异项 → 生成 .config → make menuconfig(可选) → make 编译

流程其实不复杂:

  1. make defconfig — 读取 Kconfig 树,加载 defconfig 中的差异项,生成初始 .config
  2. make menuconfig(可选)— 在 .config 基础上做微调
  3. make — 根据 .config 编译 U-Boot
  4. make savedefconfig(可选)— 从 .config 反向生成精简的 defconfig
实战建议:新板子移植时,先找一个相近板子的 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,往往能发现遗漏的选项。
注意:不要手动编辑 .config 文件!虽然技术上可行,但 .config 的格式和 Kconfig 的依赖关系很复杂,手动改很容易导致不一致。用 menuconfig 或者 make config(命令行问答式)才是正道。

嗯,关于配置体系就讲这么多。Kconfig、defconfig、menuconfig 这三者,说白了就是一套"定义-默认-交互"的配置管理框架。理解了这个框架,你就能自如地给新板子添加支持,也能快速定位配置相关的问题。