4、内核配置系统详解:Kconfig语法、Makefile与内核编译、配置选项的依赖关系、如何添加自己的配置选项
内核配置系统,说白了就是 Linux 内核的「开关面板」。你想想看,一个内核支持上千种硬件、几百个文件系统、几十种网络协议,总不能一股脑全编译进去吧?那得有多大?所以我们需要一套机制,让开发者按需选择功能模块。
这套机制由三部分组成:Kconfig(配置界面描述)、.config(配置结果存储)、Kbuild/Makefile(条件编译执行)。今天我们就把它彻底拆开看看。
核心认知:Kconfig 定义「有什么选项」,.config 记录「选了哪些」,Makefile 决定「编译什么」。三者缺一不可。
4.1 Kconfig 语法——配置界面的「语言」
Kconfig 文件散布在内核源码的各个目录下。每个驱动、每个子系统都有自己的 Kconfig。顶层 Kconfig 通过 source 语句把它们串起来。
先看一个最简例子:
config MY_DRIVER
bool "Enable My Custom Driver"
default n
help
This is a custom driver for my hardware.
Say Y here if you want to use this driver.
这里每个字段都有讲究:
| 关键字 | 含义 | 常见取值 |
|---|---|---|
config |
定义一个配置选项 | 选项名(如 MY_DRIVER) |
bool |
选项类型 | bool / tristate / int / hex / string |
default |
默认值 | y / n / m / 数值 |
depends on |
依赖关系 | 另一个 CONFIG_xxx |
select |
反向选择(自动选中) | 另一个 CONFIG_xxx |
help |
帮助信息 | 文本描述 |
类型说明:
- bool:要么编译(y),要么不编译(n)。
- tristate:三态——编译进内核(y)、编译成模块(m)、不编译(n)。驱动模块常用这个。
- int/hex:输入整数或十六进制数,比如缓冲区大小。
- string:输入字符串,比如设备路径。
我的习惯:新写驱动时,如果不确定用户会怎么用,优先用 tristate。这样既能编进内核,也能做成模块动态加载,灵活性大很多。
4.2 配置选项的依赖关系——牵一发而动全身
配置选项之间不是孤立的。你选了 A,可能 B 就必须选,C 就不能选。这就是依赖关系。
最常见的写法:
config MY_DRIVER
tristate "My Driver"
depends on I2C
depends on GPIOLIB
select REGMAP
这里的意思是:
depends on I2C:只有 I2C 子系统被启用时,MY_DRIVER 才可见。depends on GPIOLIB:同样依赖 GPIO 库。select REGMAP:一旦选了 MY_DRIVER,REGMAP 会被自动选中(反向依赖)。
注意区别:
depends on是「被动约束」——条件不满足,选项直接隐藏。select是「主动拉取」——我选了你,你就必须跟着选。
我曾经踩过的坑:滥用 select 会导致「循环依赖」。比如 A select B,B select A,配置系统直接报错。所以 select 只用于「这个选项必须依赖的底层基础设施」,不要随便拉兄弟模块。
还有一种常见场景——if 条件块:
if EXPERT
config DEBUG_OPTION
bool "Enable Debug"
default n
endif
只有 EXPERT 被启用时,DEBUG_OPTION 才会出现在菜单里。这用来隐藏「危险选项」,防止普通用户误操作。
4.3 Makefile 与内核编译——条件编译的执行者
Kconfig 只是「画菜单」,真正干活的是 Makefile。内核的 Makefile 体系叫 Kbuild,它通过读取 .config 中的变量来决定编译哪些文件。
看一个典型的驱动 Makefile:
obj-$(CONFIG_MY_DRIVER) += my_driver.o
my_driver-objs := core.o ioctl.o probe.o
这里 obj-$(CONFIG_MY_DRIVER) 是关键:
- 如果
CONFIG_MY_DRIVER=y,展开为obj-y,文件编进内核。 - 如果
CONFIG_MY_DRIVER=m,展开为obj-m,文件编译成模块。 - 如果
CONFIG_MY_DRIVER未定义或为 n,整行被忽略。
多文件模块的写法:
obj-$(CONFIG_MY_DRIVER) += my_driver.o
my_driver-y := core.o ioctl.o probe.o
用 -y 代替 -objs,效果一样。我个人更推荐 -y,因为和 obj-y 风格统一。
编译流程速览:
make menuconfig → 生成 .config → make 读取 .config → Kbuild 解析 Makefile → 条件编译 → 生成 vmlinux / .ko
make menuconfig → 生成 .config → make 读取 .config → Kbuild 解析 Makefile → 条件编译 → 生成 vmlinux / .ko
4.4 如何添加自己的配置选项——完整实战
假设我们要给一块自定义开发板添加一个 LED 驱动。步骤如下:
- 创建驱动源码目录:
drivers/leds/my_led/ - 编写 Kconfig:
# drivers/leds/my_led/Kconfig
config MY_LED
tristate "My Custom LED Driver"
depends on LEDS_CLASS
help
This is a custom LED driver for my board.
Say Y/M here to enable it.
- 编写 Makefile:
# drivers/leds/my_led/Makefile
obj-$(CONFIG_MY_LED) += my_led.o
my_led-y := main.o gpio_ops.o
- 在父目录 Kconfig 中引用:
# drivers/leds/Kconfig 末尾添加
source "drivers/leds/my_led/Kconfig"
- 在父目录 Makefile 中引用:
# drivers/leds/Makefile 末尾添加
obj-$(CONFIG_MY_LED) += my_led/
- 执行配置:
make menuconfig,找到 Device Drivers → LED Support → My Custom LED Driver,选中它。 - 编译验证:
make,检查是否生成my_led.ko或编入内核。
小技巧:添加新目录后,记得先
make menuconfig 看看选项是否出现。如果没出现,八成是父目录的 source 语句写错了路径。
4.5 知识结构总览
下面这张图把整个配置系统的脉络画清楚了:
4.6 避坑指南与调试技巧
做内核配置这么多年,我总结了几条实用经验:
- 配置不生效?先检查
.config里有没有你的选项。如果没有,说明 Kconfig 没被 source 进来。 - 编译没报错但文件没编?看看 Makefile 里的
obj-$(CONFIG_xxx)变量名是否和 Kconfig 定义的一致。大小写敏感! - 依赖关系乱套?用
make menuconfig的「/」搜索功能,输入选项名,它会显示所有依赖和被依赖关系。 - 想快速验证?直接修改
.config文件,把# CONFIG_xxx is not set改成CONFIG_xxx=y,然后make olddefconfig自动补全依赖。
我曾经犯过的错:有次在 Kconfig 里把
depends on 写成了 depends,漏了 on,结果配置界面里选项死活不出现。查了半天才发现是语法错误。所以写完 Kconfig 记得用 scripts/kconfig/conf --syncconfig 做语法检查。
嗯,配置系统就讲到这里。你只要动手写一次自己的 Kconfig,跑一遍完整的配置→编译流程,这些东西就变成肌肉记忆了。