3. Kconfig与Makefile基础:内核配置的“语法书”

做嵌入式Linux内核开发,你迟早要面对两个文件:Kconfig 和 Makefile。说白了,Kconfig 是内核的“菜单配置系统”,Makefile 是“编译执行系统”。你加一个新驱动、一个新功能,就得同时伺候好这两位。

我记得刚入行那会儿,第一次给一块 ARM 板子加 GPIO 按键驱动。我在 Kconfig 里写了个选项,make menuconfig 里也看到了,但编译死活不进去。折腾了半天,发现 Makefile 里忘了加 obj-$(CONFIG_xxx) 那一行。嗯,这种坑,我猜你也迟早会遇到。

3.1 Kconfig 语法入门

Kconfig 文件分布在各个子目录下,比如 drivers/input/、drivers/net/。内核的配置工具(menuconfig、xconfig)就是靠解析这些文件来生成配置菜单的。

3.1.1 基本结构:config 条目

一个最简单的 Kconfig 条目长这样:

config MY_DRIVER
    bool "Enable My Driver"
    default n
    help
      This is a sample driver for learning Kconfig.
  • config:关键字,后面跟选项名(习惯用大写+下划线)。
  • bool:类型,表示“要么编译进内核(y),要么不编译(n)”。
  • default n:默认关闭。我建议新加的选项默认都设 n,避免影响现有配置。
  • help:帮助信息,写清楚这个选项是干嘛的。
个人习惯:我写 help 时一定会写清楚依赖的硬件平台或芯片型号。半年后你自己回来看,绝对感谢当时的自己。

3.1.2 类型与依赖

Kconfig 支持的类型不止 bool,还有这些:

类型 含义 可选值
bool 开关选项 y / n
tristate 三态选项 y / m / n
int 整数输入 用户输入数字
hex 十六进制输入 用户输入 0x...
string 字符串输入 用户输入文本

依赖关系 是 Kconfig 里最容易出问题的地方:

config MY_DRIVER
    tristate "My Driver"
    depends on I2C
    depends on ARM
    select REGMAP_I2C
    help
      My driver requires I2C and ARM architecture.
  • depends on:前置依赖。只有 I2C 和 ARM 被选中时,这个选项才会出现。
  • select:反向选择。选中 MY_DRIVER 时,自动强制选中 REGMAP_I2C。这招要慎用,搞不好会造成依赖循环。
我曾经踩过的坑:在一个项目里用 select 强制选了某个子系统,结果那个子系统又 depends on 另一个模块,形成了环形依赖。内核配置工具直接崩溃。后来我改成 depends on + 在 help 里提示用户手动选,反而更稳。

3.1.3 menu 与 if 组织

为了让菜单不显得杂乱,可以用 menu 和 if 做分组:

menu "My Custom Drivers"

config MY_DRIVER_A
    bool "Driver A"
    default n

config MY_DRIVER_B
    bool "Driver B"
    default n

endmenu

这样在 menuconfig 里就会看到一个“My Custom Drivers”的折叠菜单,点进去才是 Driver A 和 Driver B。

3.2 Makefile 核心规则

Kconfig 定义的是“要不要编译”,Makefile 定义的是“怎么编译”。两者配合才能让代码真正进到内核里。

3.2.1 目标编译:obj-y / obj-m / obj-n

内核 Makefile 里最核心的变量就三个:

obj-y       += my_driver.o      # 编译进内核
obj-m       += my_driver.o      # 编译成模块
obj-n       += my_driver.o      # 不编译

但实际开发中,我们不会直接写死 obj-y 或 obj-n,而是通过 Kconfig 生成的宏来控制:

obj-$(CONFIG_MY_DRIVER) += my_driver.o

当 CONFIG_MY_DRIVER=y 时,等价于 obj-y += my_driver.o;当 CONFIG_MY_DRIVER=m 时,等价于 obj-m += my_driver.o;当没定义时,等价于 obj-n,不编译。

关键点:Kconfig 里选项名是 MY_DRIVER,但生成的宏是 CONFIG_MY_DRIVER。Makefile 里引用时必须带 CONFIG_ 前缀。我见过新手在这里少写 CONFIG_,编译出来驱动永远不在内核里。

3.2.2 多文件编译与编译选项

如果一个驱动由多个源文件组成:

obj-$(CONFIG_MY_DRIVER) += my_driver.o
my_driver-objs := main.o i2c_ops.o gpio_ops.o

还可以单独指定编译参数:

ccflags-y += -DDEBUG -O2
CFLAGS_my_driver.o := -DSPECIAL_FLAG
  • ccflags-y:作用于整个目录下的所有文件。
  • CFLAGS_文件名.o:只作用于单个文件。

3.3 实战:添加一个内核配置选项

下面我们完整走一遍流程。假设我们要在 drivers/char/ 下添加一个虚拟字符驱动 virt_char。

步骤 1:创建源文件

在 drivers/char/ 下创建 virt_char.c:

// drivers/char/virt_char.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

#define DEVICE_NAME "virt_char"

static int __init virt_char_init(void)
{
    printk(KERN_INFO "virt_char: driver loaded\n");
    return 0;
}

static void __exit virt_char_exit(void)
{
    printk(KERN_INFO "virt_char: driver unloaded\n");
}

module_init(virt_char_init);
module_exit(virt_char_exit);
MODULE_LICENSE("GPL");

步骤 2:修改 Kconfig

在 drivers/char/Kconfig 里添加:

config VIRT_CHAR
    tristate "Virtual Character Device Driver"
    default n
    help
      A simple virtual character device for learning.
      Say Y here to compile it into the kernel,
      or M to build it as a module.

步骤 3:修改 Makefile

在 drivers/char/Makefile 里添加:

obj-$(CONFIG_VIRT_CHAR) += virt_char.o

步骤 4:验证

  1. 执行 make menuconfig,进入 Device Drivers → Character devices,找到“Virtual Character Device Driver”。
  2. 按 Y 或 M 选中,保存退出。
  3. 执行 make 编译。如果选的是 M,会生成 virt_char.ko;选 Y,会编译进内核。
调试小技巧:如果 menuconfig 里看不到你的选项,检查 Kconfig 文件是否被父目录的 source 语句引用了。比如 drivers/char/Kconfig 必须在 drivers/Kconfig 里有 source "drivers/char/Kconfig" 才行。

3.4 知识结构图:Kconfig + Makefile 协作流程

下面这张图帮你理清整个链路:

Kconfig与Makefile协作流程 Kconfig + Makefile 协作流程 Kconfig 定义选项 config VIRT_CHAR / tristate / depends on make menuconfig 生成 .config CONFIG_VIRT_CHAR=y / m / 未定义 include/generated/autoconf.h #define CONFIG_VIRT_CHAR 1 Makefile 引用宏 obj-$(CONFIG_VIRT_CHAR) += virt_char.o 宏展开 obj-y += virt_char.o 或 obj-m += virt_char.o 编译生成 virt_char.o → vmlinux 或 virt_char.ko 自动生成 核心原则 Kconfig 决定“要不要”,Makefile 决定“怎么要” 两者通过 CONFIG_ 宏作为桥梁,缺一不可 修改后务必 make menuconfig 重新生成 .config

3.5 常见陷阱与避坑指南

  • Kconfig 里写了,menuconfig 看不到:检查父目录 Kconfig 是否有 source 语句引用你的文件。
  • menuconfig 里选了,编译没反应:检查 Makefile 里 obj-$(CONFIG_xxx) 的宏名是否和 Kconfig 一致,注意 CONFIG_ 前缀。
  • 编译报错“undefined reference”:多文件驱动时,检查 xxx-objs 是否列出了所有 .o 文件。
  • 模块加载失败“Unknown symbol”:检查 depends on 和 select 是否漏了必要的子系统。
我曾经踩过的坑:有一次我在 Kconfig 里用了 select 强制选了一个模块,但那个模块本身有 depends on BROKEN。结果配置界面直接卡死,最后只能手动删 .config 文件恢复。所以,select 一定要慎用,能用 depends on 就别用 select。