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
三态型tristatey / m (模块) / n,U-Boot 里很少用 mCONFIG_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 时,实际干了三件事:

  1. 加载 Kconfig 默认值:系统遍历所有 Kconfig 文件,生成一个“全默认”的配置。
  2. 应用 defconfig 覆盖:读取 configs/xxx_defconfig,把里面指定的选项覆盖上去。
  3. 生成 .config:把最终结果写入 .config 文件。

之后执行 make 编译时,.config 会被转换成 include/autoconf.mk(给 Makefile 用)和 include/generated/autoconf.h(给 C 代码用)。

你看,整个链路就是:Kconfig 定义 → defconfig 预设 → menuconfig 微调 → .config 生成 → 编译使用。

下面这张图能帮你更直观地理解:

U-Boot 配置生成流程 Kconfig 文件 定义选项、依赖、默认值 defconfig 文件 板级默认配置增量 menuconfig 用户交互微调 配置合并与冲突处理 .config 最终配置文件 autoconf.mk 给 Makefile 使用 autoconf.h 给 C 代码使用 执行 make xxx_defconfig → 自动完成 1-3 步;执行 make menuconfig → 手动微调第 3 步

3.5 实战:如何快速验证配置是否生效

说了这么多理论,来点实际的。假设你给一块新板子移植 U-Boot,想确认某个配置有没有被正确加载。

我的做法分三步:

  1. 检查 .config:grep CONFIG_MY_FEATURE .config,看有没有定义。
  2. 检查 autoconf.h:grep CONFIG_MY_FEATURE include/generated/autoconf.h,看 C 代码能不能用。
  3. 检查编译日志:make V=1 2>&1 | grep MY_FEATURE,看编译器有没有收到 -DCONFIG_MY_FEATURE。

如果第三步没输出,说明配置根本没传给编译器。这时候回头查 Kconfig 的依赖关系,十有八九是 depends on 没满足。

一个小技巧:在 make menuconfig 里,按 / 搜索你的选项,它会直接告诉你这个选项当前是什么状态,以及为什么被隐藏(如果有依赖没满足的话)。这比翻 Kconfig 文件快多了。

嗯,关于 U-Boot 的配置系统,核心就是这些。你只要记住:Kconfig 是规则,defconfig 是默认值,menuconfig 是调整工具,.config 是最终结果。搞清楚了这四者的关系,以后改配置、加功能、移植板子,心里就有底了。