第二章 内核配置系统详解:Kconfig语法、Makefile入门、配置选项类型与依赖关系
内核配置系统,说白了就是Linux内核的“开关面板”。你想想看,一个内核支持上千种硬件、几百个文件系统、几十种网络协议,总不能一股脑全编译进去吧?那编译出来的内核得有多大,跑起来得多慢。
配置系统就是让你按需选择——要什么功能,就打开什么开关。我刚开始接触内核时,最头疼的就是搞不清这些配置选项到底怎么组织的。后来啃了几遍Kconfig和Makefile,才算真正入了门。
2.1 Kconfig语法——配置选项的“说明书”
Kconfig文件定义了每个配置选项的名字、类型、默认值、依赖关系。它分布在源码树各层目录下,通过source指令层层包含,形成一棵配置树。
一个典型的Kconfig条目长这样:
config CONFIG_XXX
bool "Enable XXX feature"
depends on CONFIG_YYY
select CONFIG_ZZZ
default y
help
This option enables the XXX feature.
Say Y if you need this feature.
我来拆解一下每个字段的含义:
| 字段 | 含义 | 说明 |
|---|---|---|
config |
配置项标识 | 实际使用时前面会加CONFIG_前缀 |
bool/tristate/string |
选项类型 | 决定选项的取值方式 |
depends on |
依赖关系 | 只有依赖项满足时,本选项才可见 |
select |
反向选择 | 选中本选项时,自动选中被select的选项 |
default |
默认值 | 可以是y/n/m,也可以是表达式 |
help |
帮助信息 | 给开发者看的说明文字 |
help字段一定要写清楚。别偷懒。我在项目中见过太多“help: TODO”的条目,后来维护的人根本不知道这个选项是干嘛的,只能去翻代码。
2.2 配置选项类型——bool、tristate、string怎么选
这三种类型是Kconfig的基石。我分别说说它们的用法和场景。
2.2.1 bool——要么编译,要么不编译
bool选项只有两个值:y(编译进内核)或n(不编译)。适合那些必须内置的功能,比如调度器、内存管理核心代码。
config PREEMPT
bool "Preemptible Kernel"
default y
help
This option reduces the latency of the kernel...
2.2.2 tristate——三态开关,驱动模块的最爱
tristate有三个值:y(编译进内核)、m(编译为模块)、n(不编译)。绝大多数驱动都用这个类型。
config USB_STORAGE
tristate "USB Mass Storage support"
depends on USB
help
Say Y here to support USB mass storage devices...
为什么驱动喜欢用tristate?因为m模式可以动态加载卸载,调试时特别方便。我调试USB驱动时,经常把驱动编成模块,改完代码make modules,然后insmod测试,不用重启机器。
2.2.3 string——字符串参数
string用来传递字符串值,比如默认命令行参数、路径等。
config CMDLINE
string "Default kernel command string"
default "console=ttyS0,115200"
help
This string will be used as the kernel command line...
bool,结果客户想把它编成模块动态加载,发现根本选不了m。后来改成了tristate才解决问题。记住:驱动尽量用tristate,除非有特殊理由必须内置。
2.3 依赖与选择关系——配置选项的“社交网络”
配置选项之间不是孤立的。一个功能可能依赖另一个功能,也可能强制选中另一个功能。Kconfig提供了两种机制:depends on和select。
2.3.1 depends on——被动依赖
depends on的意思是:只有依赖项被选中时,本选项才可见。如果依赖项没选,本选项直接灰掉,你连选都选不了。
config SCSI
tristate "SCSI device support"
config SCSI_DEBUG
tristate "SCSI debugging driver"
depends on SCSI
上面的例子中,SCSI_DEBUG依赖SCSI。如果SCSI没选,SCSI_DEBUG根本不会出现在菜单里。
2.3.2 select——主动拉取
select是反向的:当你选中本选项时,它会自动选中被select的选项。这常用于“我需要某个基础设施”的场景。
config GPIO_SYSFS
bool "GPIO sysfs interface"
select GPIOLIB
help
Say Y here to add a sysfs interface for GPIOs.
选中GPIO_SYSFS时,GPIOLIB会被自动选上。用户不需要手动去翻菜单找GPIOLIB。
depends on是“你选我,必须先选它”;select是“你选我,我帮你选它”。前者是约束,后者是承诺。
2.4 Makefile入门——配置选项如何驱动编译
Kconfig定义了“选什么”,Makefile定义了“怎么编译”。两者通过CONFIG_前缀的变量连接。
一个典型的内核Makefile片段:
obj-$(CONFIG_USB_STORAGE) += usb-storage.o
obj-$(CONFIG_SCSI) += scsi_mod.o
obj-m += my_driver.o
这里的语法规则:
obj-y:编译进内核(对应Kconfig的y)obj-m:编译为模块(对应Kconfig的m)obj-n:不编译(对应Kconfig的n)
当CONFIG_USB_STORAGE为y时,obj-$(CONFIG_USB_STORAGE)展开为obj-y,usb-storage.o被编译进内核。如果为m,则展开为obj-m,编译为模块。
obj-y += my_test.o就行。当然,这只是调试用,正式提交还是要走Kconfig。
2.5 知识结构总览
下面这张图把Kconfig、Makefile、配置选项之间的关系串起来了:
2.6 实战建议——少走弯路
最后分享几个我在项目中积累的经验:
- 先看
.config文件:配置完成后,.config文件里记录了所有选项的最终值。想确认某个选项是否生效,直接grep CONFIG_XXX .config。 - 善用
make menuconfig的搜索功能:按/键可以搜索选项名或描述,还能看到依赖链。我调试驱动时经常用这个功能。 - 依赖关系别搞循环:
A depends on B且B depends on A,Kconfig会报错。设计新选项时,先画一下依赖图。 - 模块名和源文件名尽量一致:比如
config USB_STORAGE对应的源文件叫usb-storage.c,这样找代码时一目了然。
depends on ARCH_SOME_CHIP,结果换平台编译时,这个选项直接消失了。排查了半天才发现是依赖的平台宏没定义。后来我改用depends on ARCH_SOME_CHIP || COMPILE_TEST,这样即使目标平台没选,也能手动打开编译测试。
嗯,Kconfig和Makefile的配合,说白了就是“定义-选择-编译”这三步。把这三步理清楚,内核配置就不再是黑盒了。