2. Linux内核源码结构与构建系统入门
内核配置系统,说白了就是 Linux 内核的「开关面板」。你想想看,一个内核支持几千种硬件、几百种文件系统、几十种网络协议,总不能一股脑全编译进去吧?Kconfig 就是用来管理这些开关的。
我刚开始接触内核时,觉得 Kconfig 就是个简单的菜单描述语言。直到有一次,我写了一个驱动模块,depends on 写错了依赖关系,结果配置界面里我的选项死活不出现……嗯,从那以后我才认真啃了一遍 Kconfig 语法。
2.1 Kconfig 的核心元素
Kconfig 文件通常放在每个驱动目录下,文件名就叫 Kconfig。它由一系列配置条目组成,每个条目控制一个内核配置选项。
2.1.1 config:最基本的配置项
一个 config 条目定义一个开关变量,最终会生成 .config 文件里的 CONFIG_XXX=y 或 CONFIG_XXX=m。
config MY_DRIVER
tristate "My custom driver support"
default n
help
This is a custom driver for demonstration.
Say Y here to enable it, M to build as module.
这里 tristate 表示三态:Y(编译进内核)、M(编译为模块)、N(不编译)。如果是 bool,就只有 Y 和 N 两个选项。
tristate。调试阶段用 M 编译成模块,省得每次改代码都要重编整个内核。
2.1.2 menuconfig:带子菜单的配置项
menuconfig 和 config 的区别在于:前者可以展开一个子菜单,里面放更多配置项。
menuconfig MY_SUBSYSTEM
bool "My subsystem support"
default n
if MY_SUBSYSTEM
config MY_FEATURE_A
bool "Feature A"
default y
config MY_FEATURE_B
tristate "Feature B"
default m
endif
只有 MY_SUBSYSTEM 被选中时,MY_FEATURE_A 和 MY_FEATURE_B 才会出现在配置界面里。这个机制在管理复杂驱动时特别有用。
2.1.3 choice:单选组
当一组选项只能选一个时,用 choice。比如选择 CPU 调度算法、选择 I/O 调度器。
choice
prompt "I/O Scheduler"
default DEFAULT_DEADLINE
config DEFAULT_DEADLINE
bool "Deadline"
select MQ_IOSCHED_DEADLINE
config DEFAULT_CFQ
bool "CFQ"
select MQ_IOSCHED_CFQ
endchoice
这里 prompt 是给用户看的提示文字。default 指定默认选中项。注意:choice 里的选项必须是 bool 类型。
choice 里用了 tristate,结果配置界面直接报语法错误。内核的 Kconfig 解析器对类型检查很严格,choice 只接受 bool。
2.1.4 source:引入子目录的 Kconfig
内核的 Kconfig 是树状结构的。顶层 Kconfig 通过 source 引入各个子目录的配置。
source "drivers/my_driver/Kconfig"
source "net/my_protocol/Kconfig"
路径是相对于内核源码根目录的。我习惯在每个驱动目录下放一个独立的 Kconfig,然后在父目录里用 source 引入,这样模块化清晰,也方便别人维护。
2.2 依赖与选择:depends on 和 select
这两个关键字是 Kconfig 里最容易搞混的,我当年就吃过亏。
| 关键字 | 含义 | 方向 | 典型场景 |
|---|---|---|---|
depends on |
依赖某个选项被启用 | 被动等待 | 驱动依赖某个总线或框架 |
select |
强制启用另一个选项 | 主动拉取 | 驱动需要某个底层库 |
config MY_USB_DRIVER
tristate "My USB driver"
depends on USB
select MY_HELPER_LIB
help
This driver requires USB support and
automatically selects MY_HELPER_LIB.
这里 depends on USB 表示:如果 CONFIG_USB 没开,我的驱动在配置界面里根本不会出现。select MY_HELPER_LIB 表示:只要用户选了 MY_USB_DRIVER,MY_HELPER_LIB 就会被自动设为 Y 或 M。
depends on 是「你不开我就不出现」,select 是「你选我就带上它」。滥用 select 可能导致依赖循环,内核的 Kconfig 解析器会报错。
2.3 default 和 help
default 设置默认值,help 提供帮助信息。这两个看似简单,但用好了能省很多事。
config MY_DRIVER_DEBUG
bool "Enable debug output for my driver"
depends on MY_DRIVER
default n
help
Say Y here to enable verbose debug messages.
This will slow down the driver.
If unsure, say N.
我个人习惯:新驱动默认 n,除非是平台必须的驱动(比如串口、时钟控制器)才默认 y。调试选项一律默认 n,避免影响性能。
y,结果客户反馈驱动跑起来特别慢。查了半天才发现是调试日志一直在刷。从那以后,所有调试相关的配置我都默认 n。
2.4 Makefile 与内核编译的关联
Kconfig 定义「要不要编译」,Makefile 定义「怎么编译」。两者通过 CONFIG_XXX 变量连接。
# Kconfig
config MY_DRIVER
tristate "My driver"
default n
# Makefile
obj-$(CONFIG_MY_DRIVER) += my_driver.o
当 CONFIG_MY_DRIVER=y 时,obj-y 会把 my_driver.o 编进内核;当 CONFIG_MY_DRIVER=m 时,obj-m 会把它编成 .ko 模块;当 n 时,整行被忽略。
如果驱动由多个源文件组成:
obj-$(CONFIG_MY_DRIVER) += my_driver.o
my_driver-objs := main.o helper.o init.o
或者用更简洁的写法:
obj-$(CONFIG_MY_DRIVER) := my_driver.o
my_driver-y := main.o helper.o init.o
CONFIG_ 前缀完全一致。大小写敏感,少写一个字母,编译时就找不到文件。我见过有人把 CONFIG_MY_DRIVER 写成 CONFIG_my_driver,结果编出来的 .ko 文件是空的。
2.5 知识体系总览
下面这张图总结了 Kconfig 和 Makefile 的协作关系:
2.6 实战建议
写 Kconfig 时,我总结了几条经验:
- 命名规范:驱动选项用
CONFIG_<DRIVER_NAME>,调试选项加_DEBUG后缀,一目了然。 - 依赖最小化:能用
depends on就别用select,避免引入不必要的依赖链。 - 帮助信息写清楚:至少说明这个驱动是干什么的、依赖什么硬件、选 Y 还是 M 的区别。我见过太多 Kconfig 的 help 就写一行 "Say Y here",等于没写。
- 测试配置:写完 Kconfig 后,运行
make menuconfig看看你的选项是否出现在正确的位置,依赖关系是否生效。