内核配置基础:Kconfig 与 Makefile 的裁剪哲学
做嵌入式驱动开发,你迟早要面对一个问题:内核为什么这么大?
我刚开始接触 Linux 内核时,编译一次要等半小时。后来发现,里面有一大半驱动我这辈子都用不上。Wi-Fi 协议栈、显卡驱动、文件系统……我的板子就是个跑裸机的 Cortex-M,要这些干嘛?
所以,裁剪是必须的。而裁剪的根基,就是 Kconfig 和 Makefile。说白了,Kconfig 是「菜单」,Makefile 是「菜谱」。你点菜,厨师按菜谱做。今天我们就聊聊这两样东西的哲学——怎么点菜,怎么改菜谱,才能让内核又小又快。
Kconfig:内核的「点菜系统」
Kconfig 文件定义了内核的配置选项。每个驱动、每个功能,都有一个对应的配置项。比如:
config MY_DRIVER
bool "Enable My Custom Driver"
depends on ARM
help
This driver does something cool on ARM platforms.
这个配置项会出现在 make menuconfig 的界面里。你选中它,驱动就会被编译;不选,就跳过。
我个人习惯,在写驱动时一定会加 depends on 和 select。为什么?因为依赖关系不写清楚,别人配置时就会踩坑。我曾经见过一个同事,把 I2C 驱动配上了,但忘了配 I2C 核心模块,结果编译报错,查了半天。
核心原则:Kconfig 的裁剪哲学是「按需加载」。你不需要的功能,一个都不要选。选得越少,内核越小,启动越快。
Makefile:编译的「指挥棒」
Kconfig 决定了「要不要编译」,Makefile 决定了「怎么编译」。典型的写法:
obj-$(CONFIG_MY_DRIVER) += my_driver.o
如果 CONFIG_MY_DRIVER 被设为 y,my_driver.o 就会被编译进内核。如果是 m,就编译成模块。如果是空,就不编译。
这里有个小技巧:模块化 vs 内建。我建议,启动时必须用的驱动(比如串口、时钟)内建;不常用的(比如 USB 摄像头、Wi-Fi)做成模块。这样内核镜像小,启动快,需要时再加载模块。
我的经验:在项目初期,把所有驱动都编译成模块。调试时用 insmod 加载,方便快速迭代。等稳定了,再把关键驱动内建。
裁剪哲学:少即是多
裁剪不是盲目删东西。你得知道哪些是必需的,哪些是多余的。我总结了一个「三问法」:
- 这个驱动我的硬件需要吗?——不需要就关掉。
- 这个功能我的系统用得上吗?——用不上就关掉。
- 这个模块能延迟加载吗?——能就做成模块。
举个例子。我在做一款工业控制器时,板子上只有 SPI Flash、UART 和 GPIO。我直接把所有网络驱动、音频驱动、显卡驱动全关了。内核从 4MB 缩到 800KB,启动时间从 8 秒降到 2 秒。
注意:不要为了追求小内核而关掉必要的依赖。比如你选了 SPI 驱动,但忘了选 SPI 核心,编译会报错。Kconfig 的 depends on 就是干这个的,别绕过它。
Kconfig 与 Makefile 的协作流程
下面这张图展示了从配置到编译的完整流程:
常见裁剪策略对比
不同的场景,裁剪策略也不同。我整理了一个表格:
| 场景 | 裁剪策略 | 典型配置 | 效果 |
|---|---|---|---|
| 裸机系统 | 全内建,无模块 | 只选必要驱动 | 内核最小,启动最快 |
| 嵌入式 Linux | 关键驱动内建,其余模块 | 串口、时钟内建;Wi-Fi、USB 模块 | 平衡大小与灵活性 |
| 开发板调试 | 全模块化 | 所有驱动编译为模块 | 方便热插拔调试 |
| 生产环境 | 严格裁剪 | 只保留硬件必需的驱动 | 安全、稳定、高效 |
避坑指南
做裁剪时,有几个坑我踩过,分享给你:
- 依赖链断裂:我曾经关掉了一个看似无关的驱动,结果导致整个 USB 子系统罢工。后来发现那个驱动是 USB 核心的依赖。所以,关掉之前先看
depends on和select。 - 模块加载顺序:如果驱动 A 依赖驱动 B,B 必须先加载。我建议用
modprobe而不是insmod,它会自动处理依赖。 - 调试信息过多:开发时打开
DEBUG没问题,但发布前一定要关掉。否则内核日志会被刷爆,性能也会下降。
小技巧:用 make savedefconfig 生成最小配置。它会从当前 .config 中提取出非默认的选项,方便你后续维护。
总结
Kconfig 和 Makefile 是内核裁剪的基石。Kconfig 定义「要不要」,Makefile 定义「怎么编」。裁剪的核心哲学是:只保留你需要的,其他的都砍掉。
嗯,说起来简单,做起来需要经验。但只要你掌握了这三步——分析需求、配置选项、验证编译——你就能做出又快又小的内核。