第二章 内核配置系统详解: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 帮助信息 给开发者看的说明文字
我的习惯:写Kconfig时,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,编译为模块。

我个人的调试技巧:临时想编译一个文件进内核,又不想改Kconfig?直接在Makefile里写obj-y += my_test.o就行。当然,这只是调试用,正式提交还是要走Kconfig。

2.5 知识结构总览

下面这张图把Kconfig、Makefile、配置选项之间的关系串起来了:

内核配置系统知识结构 Kconfig 配置定义 定义选项名称、类型 设置默认值 声明依赖关系 编写帮助信息 通过 source 组织层级 配置选项类型 bool (y/n) tristate (y/m/n) string (字符串) int/hex (数值) 依赖与选择 depends on 被动依赖,约束可见性 select 主动拉取,自动选中 Makefile 编译控制 obj-y → 编译进内核 | obj-m → 编译为模块 | obj-n → 不编译 通过 $(CONFIG_XXX) 变量与 Kconfig 联动 编译产物 vmlinux(内核镜像) + .ko(可加载模块) 配置流程 make menuconfig → .config → make

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,这样找代码时一目了然。
我曾经踩过的坑:有一次我写了一个驱动,Kconfig里写了depends on ARCH_SOME_CHIP,结果换平台编译时,这个选项直接消失了。排查了半天才发现是依赖的平台宏没定义。后来我改用depends on ARCH_SOME_CHIP || COMPILE_TEST,这样即使目标平台没选,也能手动打开编译测试。

嗯,Kconfig和Makefile的配合,说白了就是“定义-选择-编译”这三步。把这三步理清楚,内核配置就不再是黑盒了。