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

4.4 如何添加自己的配置选项——完整实战

假设我们要给一块自定义开发板添加一个 LED 驱动。步骤如下:

  1. 创建驱动源码目录:drivers/leds/my_led/
  2. 编写 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.
  1. 编写 Makefile:
# drivers/leds/my_led/Makefile
obj-$(CONFIG_MY_LED) += my_led.o
my_led-y := main.o gpio_ops.o
  1. 在父目录 Kconfig 中引用:
# drivers/leds/Kconfig 末尾添加
source "drivers/leds/my_led/Kconfig"
  1. 在父目录 Makefile 中引用:
# drivers/leds/Makefile 末尾添加
obj-$(CONFIG_MY_LED) += my_led/
  1. 执行配置:make menuconfig,找到 Device Drivers → LED Support → My Custom LED Driver,选中它。
  2. 编译验证:make,检查是否生成 my_led.ko 或编入内核。
小技巧:添加新目录后,记得先 make menuconfig 看看选项是否出现。如果没出现,八成是父目录的 source 语句写错了路径。

4.5 知识结构总览

下面这张图把整个配置系统的脉络画清楚了:

内核配置系统知识结构 Kconfig 定义配置选项与菜单 .config 存储用户选择结果 Kbuild/Makefile 条件编译执行者 Kconfig 核心语法 • config / menuconfig • bool / tristate / int / string • depends on / select • default / range / help .config 文件格式 • CONFIG_xxx=y • CONFIG_xxx=m • CONFIG_xxx=数值 • # CONFIG_xxx is not set Kbuild 编译规则 • obj-y / obj-m / obj-n • xxx-y := file1.o file2.o • ccflags-y / ldflags-y • subdir-ccflags-y 依赖关系与实战要点 • depends on:被动约束,条件不满足时选项隐藏 • select:主动拉取,自动选中依赖项(慎用循环依赖) • 添加新选项三步走:写 Kconfig → 写 Makefile → 父目录 source / obj 引用 • 调试技巧:make menuconfig 检查可见性,make savedefconfig 导出最小配置

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,跑一遍完整的配置→编译流程,这些东西就变成肌肉记忆了。