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
一个典型的内核配置定制流程如下:
- 在 BSP 层或产品层放置基础
defconfig,作为该硬件平台的默认配置基线。 - 把功能差异拆成多个
.cfg片段,例如can.cfg、usb-gadget.cfg、debug.cfg,每个片段只负责一组相关配置。 - 在
.bbappend中通过SRC_URI按顺序声明这些片段,顺序即优先级。 - 执行
bitbake -c menuconfig virtual/kernel交互式检查,确认合并结果符合预期。 - 如需固化修改,用
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/产品 defconfig | recipes-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 来发现。
五、误区与复盘
误区一:直接提交 menuconfig 生成的完整 .config。完整 .config 包含大量与架构、版本相关的默认项,一旦内核升级,这些项会大量失效或产生冲突。正确做法是用 diffconfig 只导出差异片段。
误区二:忽略片段顺序。很多配置冲突并非配置项本身错误,而是两个片段对同一项给出了相反值,后声明的片段“悄悄”覆盖了前面的。排查时应先检查 SRC_URI 顺序,再看合并后的 .config。
误区三:把调试配置混入正式片段。调试期开启的 CONFIG_DEBUG_INFO、CONFIG_DYNAMIC_DEBUG 等会显著增大内核体积并影响性能。应单独放在 debug.cfg 中,发布时从 SRC_URI 移除。
排障提示:当配置未生效时,按以下顺序检查:SRC_URI 是否包含该片段 → 片段顺序是否被后续片段覆盖 → 合并后的 .config 中该项实际值 → 是否存在 Kconfig 依赖未满足。四步定位,绝大多数配置问题都能快速收敛。
掌握 defconfig、配置片段、menuconfig 与合并策略,本质上是在建立一套“配置即代码”的工程习惯。基线稳定、增量清晰、顺序可控、差异可审,内核定制才能从一次性的手工调试,变成可重复、可维护的标准化流程。