4. 内核配置机制

在 Yocto 中定制 Linux 内核,最核心的动作不是改代码,而是改配置。内核配置决定了哪些驱动被编译进内核、哪些编译成模块、哪些功能被彻底关闭。Yocto 通过一套“基础 defconfig + 配置片段 + 优先级合并”的机制,把内核配置从一次性的手工操作,变成可版本化、可复用、可审计的工程资产。理解这套机制,是内核定制能否稳定落地的分水岭。

一、核心原理:配置从哪里来,谁覆盖谁

Yocto 内核配置的最终结果,来自多个来源的叠加。最底层是内核源码自带的架构默认配置,通常以 defconfig 形式存在;其上是 BSP 层或产品层提供的 defconfig;再往上是若干 .cfg 配置片段;最后是 menuconfig 交互式修改后保存的 .config。这些来源并非简单拼接,而是按优先级顺序合并,高优先级覆盖低优先级。

关键点在于:Yocto 并不直接使用你手工生成的 .config,而是通过 do_kernel_configme 任务,把 defconfig 与所有 SRC_URI 中声明的配置片段,按顺序喂给内核的 merge_config.sh 脚本,生成一个临时的合并配置,再交给后续编译任务。因此,配置片段之间的顺序,直接决定了最终生效值。

一句话记忆:defconfig 定基线,片段做增量,顺序定胜负,menuconfig 只用于验证和导出片段。

二、方法流程:从 defconfig 到最终 .config

一个典型的内核配置定制流程如下:

  1. 在 BSP 层或产品层放置基础 defconfig,作为该硬件平台的默认配置基线。
  2. 把功能差异拆成多个 .cfg 片段,例如 can.cfg、usb-gadget.cfg、debug.cfg,每个片段只负责一组相关配置。
  3. 在 .bbappend 中通过 SRC_URI 按顺序声明这些片段,顺序即优先级。
  4. 执行 bitbake -c menuconfig virtual/kernel 交互式检查,确认合并结果符合预期。
  5. 如需固化修改,用 bitbake -c diffconfig virtual/kernel 导出差异片段,而不是直接提交 .config。
# 在 linux-yocto_%.bbappend 中声明配置片段
FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"

SRC_URI += " \
    file://defconfig \
    file://can.cfg \
    file://usb-gadget.cfg \
    file://debug.cfg \
"

# 片段顺序即合并优先级,后声明的覆盖先声明的

执行 diffconfig 后,Yocto 会输出一个只包含差异项的 .cfg 文件,内容形如:

CONFIG_CAN=y
CONFIG_CAN_RAW=y
CONFIG_USB_GADGET=y
# CONFIG_DEBUG_INFO is not set

这个文件就是可以长期维护的配置片段,它比完整 .config 小得多,也更容易审查和复用。

三、配置优先级与合并策略

Yocto 内核配置的合并遵循“后覆盖前”的原则。具体来说,defconfig 先被加载,然后按 SRC_URI 中出现的顺序依次应用每个 .cfg。如果两个片段对同一配置项给出不同值,后出现的片段生效。此外,内核自身的 Kconfig 依赖关系会在合并后做一次一致性检查,某些被依赖项关闭时,依赖它的配置会被自动关闭或回退。

配置来源典型位置优先级适用场景
架构默认 defconfig内核源码 arch/arm/configs最低内核自带基线
BSP/产品 defconfigrecipes-kernel/linux/files低硬件平台基线
功能配置片段 .cfg同上,按 SRC_URI 顺序中按功能拆分增量
调试/临时片段bbappend 末尾追加高调试期临时覆盖
menuconfig 导出片段diffconfig 生成最高(若加入 SRC_URI)固化交互修改

实践建议:把“平台基线”和“功能增量”分开管理。defconfig 只放该硬件必须的配置,功能片段按模块命名,调试片段单独存放且不进入正式发布。这样合并顺序清晰,冲突也容易定位。

四、案例与场景:CAN 与 USB Gadget 的配置合并

假设某产品基于 i.MX6 平台,需要启用 CAN 总线和 USB Gadget 串口功能。基础 defconfig 已包含网络和存储配置,但未开启 CAN 和 Gadget。此时新增两个片段:

# can.cfg
CONFIG_CAN=y
CONFIG_CAN_RAW=y
CONFIG_CAN_FLEXCAN=y

# usb-gadget.cfg
CONFIG_USB_GADGET=y
CONFIG_USB_CONFIGFS=y
CONFIG_USB_CONFIGFS_SERIAL=y

在 SRC_URI 中先声明 can.cfg,再声明 usb-gadget.cfg。若两者都未与 defconfig 冲突,合并后 .config 中这些项均为 y。但如果 defconfig 中已有 # CONFIG_CAN is not set,则 can.cfg 中的 CONFIG_CAN=y 会覆盖它,因为片段优先级高于 defconfig。

更复杂的情况是:usb-gadget.cfg 中开启了 CONFIG_USB_CONFIGFS_SERIAL,而该选项依赖 CONFIG_USB_CONFIGFS。如果某个后续片段误将 CONFIG_USB_CONFIGFS 关闭,内核合并阶段会因依赖不满足而自动关闭 CONFIG_USB_CONFIGFS_SERIAL,最终结果与预期不符。这类问题必须通过 menuconfig 或检查合并后的 .config 来发现。

Yocto 内核配置合并流程与优先级 架构默认 defconfig 优先级:最低 BSP/产品 defconfig 优先级:低 功能片段 .cfg 优先级:中(按顺序) 调试/临时片段 优先级:高 menuconfig 导出片段 优先级:最高 merge_config.sh 按 SRC_URI 顺序 后覆盖前 依赖一致性检查 最终 .config CONFIG_CAN=y CONFIG_USB_GADGET=y CONFIG_USB_CONFIGFS=y # CONFIG_DEBUG_INFO is not set 合并策略要点 1. defconfig 先加载,片段按 SRC_URI 顺序依次应用 2. 同一配置项后声明者覆盖先声明者 3. Kconfig 依赖不满足时,相关项会被自动关闭 4. 用 diffconfig 导出差异片段,而非提交完整 .config 优先级由低到高 ↓

五、误区与复盘

误区一:直接提交 menuconfig 生成的完整 .config。完整 .config 包含大量与架构、版本相关的默认项,一旦内核升级,这些项会大量失效或产生冲突。正确做法是用 diffconfig 只导出差异片段。

误区二:忽略片段顺序。很多配置冲突并非配置项本身错误,而是两个片段对同一项给出了相反值,后声明的片段“悄悄”覆盖了前面的。排查时应先检查 SRC_URI 顺序,再看合并后的 .config。

误区三:把调试配置混入正式片段。调试期开启的 CONFIG_DEBUG_INFO、CONFIG_DYNAMIC_DEBUG 等会显著增大内核体积并影响性能。应单独放在 debug.cfg 中,发布时从 SRC_URI 移除。

排障提示:当配置未生效时,按以下顺序检查:SRC_URI 是否包含该片段 → 片段顺序是否被后续片段覆盖 → 合并后的 .config 中该项实际值 → 是否存在 Kconfig 依赖未满足。四步定位,绝大多数配置问题都能快速收敛。

掌握 defconfig、配置片段、menuconfig 与合并策略,本质上是在建立一套“配置即代码”的工程习惯。基线稳定、增量清晰、顺序可控、差异可审,内核定制才能从一次性的手工调试,变成可重复、可维护的标准化流程。