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:帮助信息,写清楚这个选项是干嘛的。
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。这招要慎用,搞不好会造成依赖循环。
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,不编译。
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:验证
- 执行
make menuconfig,进入 Device Drivers → Character devices,找到“Virtual Character Device Driver”。 - 按 Y 或 M 选中,保存退出。
- 执行
make编译。如果选的是 M,会生成virt_char.ko;选 Y,会编译进内核。
source 语句引用了。比如 drivers/char/Kconfig 必须在 drivers/Kconfig 里有 source "drivers/char/Kconfig" 才行。
3.4 知识结构图:Kconfig + Makefile 协作流程
下面这张图帮你理清整个链路:
3.5 常见陷阱与避坑指南
- Kconfig 里写了,menuconfig 看不到:检查父目录 Kconfig 是否有
source语句引用你的文件。 - menuconfig 里选了,编译没反应:检查 Makefile 里
obj-$(CONFIG_xxx)的宏名是否和 Kconfig 一致,注意 CONFIG_ 前缀。 - 编译报错“undefined reference”:多文件驱动时,检查
xxx-objs是否列出了所有 .o 文件。 - 模块加载失败“Unknown symbol”:检查
depends on和select是否漏了必要的子系统。
select 强制选了一个模块,但那个模块本身有 depends on BROKEN。结果配置界面直接卡死,最后只能手动删 .config 文件恢复。所以,select 一定要慎用,能用 depends on 就别用 select。