3. 内核配置基础:配置系统组成
说实话,我刚接触内核配置那会儿,看着 make menuconfig 出来的界面,心里是有点懵的。那么多选项,有的打 Y,有的打 M,有的干脆空着。到底谁管着谁?
后来我才明白,这一切的背后,是两套东西在起作用:Kconfig 和 Makefile。一个管“有什么选项”,一个管“怎么编译”。今天我们就把它拆开看看。
3.1 配置系统的“骨架”:Kconfig 语法
Kconfig 文件,说白了就是内核配置菜单的“剧本”。它定义了每个选项的名字、类型、默认值,以及谁依赖谁。你看到的 menuconfig 界面,就是解析这些文件渲染出来的。
一个典型的 Kconfig 条目长这样:
config CONFIG_EXAMPLE
bool "Enable example feature"
default y
depends on CONFIG_OTHER_FEATURE
help
Say Y here if you want to enable the example feature.
If unsure, say N.
这里有几个关键字段:
- config:声明一个配置选项,后面跟的是宏名字(通常带
CONFIG_前缀)。 - bool / tristate / string / hex / int:选项的类型,决定了你能选什么值。
- default:默认值。不写的话,bool 默认是 n,tristate 默认也是 n。
- depends on:依赖关系。这个选项只有在依赖项被选中时才会出现。
- help:帮助信息,给开发者看的。
3.2 配置选项的五种类型
这是最基础,也最容易搞混的地方。我见过不少新手把 bool 和 tristate 用错,导致驱动只能编进内核,不能作为模块加载。
| 类型 | 可选值 | 典型用途 |
|---|---|---|
| bool | y / n | 开关选项,要么编进内核,要么不编 |
| tristate | y / m / n | 驱动选项,可编进内核、可编成模块、可不编 |
| string | 任意字符串 | 路径、名称、版本号等文本信息 |
| hex | 十六进制数 | 地址、掩码、寄存器值 |
| int | 十进制整数 | 数量、大小、超时值等 |
举个例子,你写一个网卡驱动:
config MY_NET_DRIVER
tristate "My network driver"
default m
help
This is a tristate driver. You can build it as a module.
config MY_NET_DEBUG
bool "Enable debug output"
depends on MY_NET_DRIVER
default n
help
Say Y to enable verbose debug messages.
config MY_NET_TX_QUEUE_SIZE
int "TX queue size (16-1024)"
range 16 1024
default 256
help
Number of descriptors in the TX ring.
config MY_NET_BASE_ADDR
hex "Base address (e.g. 0x10000000)"
default 0x10000000
help
Memory-mapped base address of the device.
bool,结果客户想把它编成模块加载,死活不行。后来查了半天才发现是类型写错了。所以,驱动选项一律用 tristate,除非你确定它必须编进内核。
3.3 依赖关系与选择逻辑
配置选项不是孤立的。它们之间有依赖、有互斥、有反向选择。理解这些关系,才能避免配出“死锁”或“缺胳膊少腿”的内核。
3.3.1 depends on:正向依赖
最常用的依赖关系。A 依赖 B,那么只有 B 被选中时,A 才会出现在菜单里。
config A
bool "Feature A"
depends on B
如果 B 没选,A 就不可见。这很直观。
3.3.2 select:反向依赖
有时候,你选了 A,就自动把 B 也选上。这叫 select。
config A
bool "Feature A"
select B
选了 A,B 自动变成 y。注意,select 会绕过 depends on 检查,容易造成依赖冲突。我一般只在“A 必须依赖 B 才能工作”时才用,而且会加注释说明。
depends on 就别用 select。select 是“强拉”,容易把依赖关系搞乱。我在一个项目里见过 select 链导致循环依赖,编译直接报错。
3.3.3 choice:互斥选择
一组选项里只能选一个。比如 CPU 调度策略、内存分配算法等。
choice
prompt "Scheduling policy"
default SCHED_CFS
config SCHED_CFS
bool "Completely Fair Scheduler"
config SCHED_RT
bool "Real-time Scheduler"
config SCHED_IDLE
bool "Idle Scheduler"
endchoice
这里只能选一个,选了这个,其他的自动取消。
3.3.4 if / endif:条件块
当某个条件成立时,才展示一组选项。
if EXPERT
config DEBUG_OPTION
bool "Enable debug"
default n
endif
只有 EXPERT 被选中时,DEBUG_OPTION 才会出现。这常用于隐藏“危险”选项。
3.4 Makefile 规则:配置如何变成编译
Kconfig 定义了“有什么”,Makefile 则定义了“怎么编”。内核的 Makefile 通过 obj-$(CONFIG_xxx) 来根据配置决定是否编译某个文件。
# 如果 CONFIG_MY_DRIVER 是 y,就编译 my_driver.o 并链接进内核
# 如果是 m,就编译成 my_driver.ko 模块
# 如果是 n,就不编译
obj-$(CONFIG_MY_DRIVER) += my_driver.o
# 多个文件可以这样写
my_driver-objs := main.o helper.o
obj-$(CONFIG_MY_DRIVER) += my_driver.o
这里有个细节:obj-y 表示编进内核,obj-m 表示编成模块,obj-n 表示不编。而 $(CONFIG_MY_DRIVER) 的值就是 y、m 或 n,Makefile 自动匹配。
obj-$(CONFIG_xxx) 放在一起,别散落在文件各处。这样以后维护时,一眼就能看到哪些文件受哪个配置控制。
3.5 整体流程:从 Kconfig 到 .config
为了让你更直观地理解,我画了一张图,展示配置系统的完整流程:
3.6 实战:一个完整的配置示例
假设我们要添加一个虚拟字符设备驱动。完整的配置系统包括三部分:
1. Kconfig 文件(放在 drivers/char/Kconfig 里)
config VIRTUAL_CHAR
tristate "Virtual character device driver"
default n
help
A simple virtual character device for testing.
Say M to build as a module, Y to build into kernel.
config VIRTUAL_CHAR_DEBUG
bool "Virtual char device debug"
depends on VIRTUAL_CHAR
default n
help
Enable debug output for virtual char device.
2. Makefile 文件(放在 drivers/char/Makefile 里)
obj-$(CONFIG_VIRTUAL_CHAR) += virtual_char.o
virtual_char-$(CONFIG_VIRTUAL_CHAR_DEBUG) += debug.o
3. 生成的 .config 片段
CONFIG_VIRTUAL_CHAR=m
CONFIG_VIRTUAL_CHAR_DEBUG=y
这样,当你运行 make menuconfig 时,就能看到“Virtual character device driver”这个选项,选 M 后,virtual_char.c 会被编译成 virtual_char.ko 模块。如果同时选了 debug,debug.c 也会被编译进去。
virtual_char-$(CONFIG_VIRTUAL_CHAR_DEBUG) 这种写法,是“条件编译子文件”的惯用技巧。但前提是 virtual_char.o 本身已经被 obj-$(CONFIG_VIRTUAL_CHAR) 选中了,否则这个条件分支不会生效。
3.7 避坑指南:我踩过的几个坑
- 依赖循环:A select B,B select A,编译直接报错。解决办法:用
depends on替代select,或者重新设计依赖关系。 - 类型不匹配:在 Makefile 里用
obj-$(CONFIG_xxx),但 Kconfig 里写的是bool。bool 只有 y/n,没有 m,所以模块编译会失败。记住:驱动用 tristate,开关用 bool。 - 默认值陷阱:Kconfig 的
default只影响“第一次配置”时的值。如果你已经有一个.config文件,修改 Kconfig 的默认值不会自动更新它。需要make olddefconfig才能生效。 - help 信息缺失:我见过一些内核模块,Kconfig 里 help 是空的。结果别人想用这个模块,根本不知道它是干嘛的。写 help 不是形式主义,是给未来的自己留条后路。