3、内核配置系统详解:menuconfig图形化配置、defconfig与.config文件、配置项依赖关系

内核配置,说白了就是决定「我的芯片上要跑哪些功能,砍掉哪些包袱」。
我见过不少新手,拿到一个开发板就直接 make menuconfig 乱选一通,结果编译出来的内核镜像比整个 Flash 还大,或者某个驱动死活不工作——其实就是配置依赖没理清楚。

这一章,我就带你把这套配置系统彻底搞明白。你以后裁剪内核、适配外设,心里就有底了。

3.1 menuconfig:图形化配置界面

在 Tina Linux 的 SDK 根目录下,执行:

make menuconfig

你会看到一个蓝底灰字的界面。嗯,别嫌它丑,这玩意儿在嵌入式领域可是标配。
它的操作逻辑其实很简单:上下键移动、空格键切换选中状态、回车键进入子菜单。

每个配置项前面有三种状态:

符号 含义 说明
[ ] 不编译 该功能被完全排除
[M] 编译为模块 以 .ko 形式存在,动态加载
[*] 编译进内核 直接内建到 zImage 中

我个人习惯是:能编进内核的就别用模块。为什么?
嵌入式设备资源有限,模块加载还要额外的文件系统和启动脚本,徒增复杂度。除非你确实需要动态插拔驱动,否则一律 [*]。

小技巧: 在 menuconfig 界面按 / 键可以搜索配置项名称。比如你想找 I2C 相关的配置,输入 I2C 回车,所有包含 I2C 的选项都会列出来,连路径都给你标好了。

3.2 defconfig 与 .config:两种配置文件的博弈

你每次在 menuconfig 里改完配置,保存后其实只生成了一个文件——.config。
但这个文件是「临时」的。如果你执行 make distclean,它就被删了。

那怎么让配置「固化」下来?
答案就是 defconfig。

在 Tina Linux 中,每个芯片平台都有自己的默认配置,存放在:

target/allwinner/<芯片型号>/defconfig

比如 target/allwinner/v853/defconfig。
这个文件是「精简版」的 .config,只记录与默认值不同的选项。它的好处是:可读性强、体积小、适合版本管理。

我举个例子你就明白了:

对比项 .config defconfig
文件大小 几千行 几十到几百行
记录方式 所有选项 仅差异项
用途 实际编译 配置模板
是否可删除 可重新生成 建议保留

工作流程是这样的:

  1. make defconfig —— 从 defconfig 生成 .config
  2. make menuconfig —— 手动微调 .config
  3. make savedefconfig —— 把当前 .config 的差异写回 defconfig

我曾经在一个量产项目上吃过亏:同事直接改了 .config 就提交到 Git,结果别人拉下来 make distclean 后配置全丢了。从那以后,我要求团队必须用 savedefconfig 更新 defconfig,.config 永远不入库。

注意: 不要手动编辑 .config 文件!虽然它也是文本格式,但选项之间的依赖关系极其复杂,你改了一个 y 为 n,可能连带把十几个依赖项都搞崩了。老老实实用 menuconfig 或者 scripts/config 工具。

3.3 配置项依赖关系:为什么你选不了某个选项?

你有没有遇到过这种情况:
进入 menuconfig,看到一个功能,想选 [*],结果按空格没反应,选项是灰色的?

这不是 bug,是 依赖关系 在起作用。

每个配置项在 Kconfig 文件中定义时,都可以声明它依赖哪些其他选项。比如:

config TOUCHSCREEN_GT9XX
    tristate "Goodix GT9xx touchscreen"
    depends on I2C
    depends on GPIOLIB || COMPILE_TEST
    help
      Say Y here if you have a Goodix GT9xx touchscreen.

这段代码的意思是:
- 要选 GT9xx 触摸驱动,必须先开启 I2C
- 同时需要 GPIOLIB 或者处于编译测试模式

如果 I2C 没开,GT9xx 的选项就是灰色的,你根本选不了。

依赖关系有三种常见形式:

关键字 含义 示例
depends on 强依赖 不满足则无法选中
select 反向选择 选中本项时自动开启另一项
imply 弱建议 建议开启,但不强制

你想想看,如果每个驱动都独立无依赖,那内核配置界面会乱成什么样?
依赖链其实是在保护你——防止你选了一个「孤零零」的功能,编译出来缺胳膊少腿。

实战经验: 我在适配一款 SPI 接口的 LCD 屏幕时,发现 FB_TFT 选项死活选不了。查了 Kconfig 才发现它 depends on SPI,而 SPI 子系统默认没开。先回去把 SPI 选上,再回来就能选了。这种「先开父项,再选子项」的思路,你以后会经常用到。

3.4 知识结构图:配置系统的核心逻辑

下面这张图,把整个配置系统的流转关系画清楚了:

内核配置系统核心流程 defconfig (精简模板,仅差异项) make defconfig (生成完整 .config) .config (完整配置,用于编译) make menuconfig (手动微调配置) make savedefconfig (差异写回 defconfig) 配置项依赖关系 depends on → 强依赖,不满足则无法选中 select → 反向选择,选中本项自动开启另一项 imply → 弱建议,建议开启但不强制

3.5 避坑指南:我踩过的三个配置坑

做内核配置这么多年,有些坑我至今记忆犹新。分享给你,希望你少走弯路。

  • 坑一:依赖链断裂
    有一次我裁剪内核,把 INPUT 子系统关了,结果触摸屏、按键、鼠标全都不工作。查了半天才发现,所有输入设备都 depends on INPUT。嗯,这个教训让我学会了「先看依赖树,再动手裁剪」。
  • 坑二:模块与内建混用导致启动失败
    我把根文件系统需要的驱动(比如 SD 卡控制器)编成了模块 [M],结果内核启动时根本挂载不了根文件系统——因为模块在根文件系统里,而根文件系统又需要这个模块才能访问。死循环了。所以,启动必需的驱动一定要编进内核。
  • 坑三:defconfig 与 .config 不同步
    团队协作时,有人改了 .config 但没执行 savedefconfig,导致其他人拉代码后编译出来的内核行为不一致。后来我在 CI 流程里加了一步:每次提交前自动检查 defconfig 是否与 .config 同步。
我的建议: 每次做完配置修改,顺手执行一下 make savedefconfig,然后把 defconfig 提交到 Git。这个习惯能帮你省掉很多排查「为什么你的内核和我的不一样」的时间。

好了,这一章的内容就到这里。配置系统是内核裁剪的基石,你把 menuconfig、defconfig、.config 和依赖关系搞透了,后面裁剪内核、适配外设就会顺手很多。