3. Linux内核裁剪:内核配置系统与编译优化
内核裁剪这事儿,说白了就是「去掉你不需要的,留下你必需的」。很多刚入行的朋友总觉得内核越大越稳,其实恰恰相反——一个臃肿的内核不仅占存储、吃内存,还会拖慢启动速度,甚至引入安全风险。我在一个工业控制项目里就吃过这个亏,当时默认配置编译出来的内核有 8MB,Flash 只有 4MB,硬是塞不进去。后来花了整整两天裁剪,最后压到 2.3MB,跑得比原来还稳。
这一章,我就把内核裁剪的核心工具链——Kconfig、Makefile、编译选项——掰开揉碎了讲清楚。
3.1 Kconfig:内核的「菜单系统」
Kconfig 是内核配置的骨架。你运行 make menuconfig 时看到的那个图形界面,背后就是 Kconfig 文件在驱动。
每个内核子系统、驱动、功能模块,都会有一个 Kconfig 文件。它的语法其实不复杂,核心就几个关键字:
| 关键字 | 作用 | 示例 |
|---|---|---|
config |
定义一个配置选项 | config MY_DRIVER |
tristate |
三态:不编译/编译进内核/编译为模块 | tristate "My Driver Support" |
bool |
二态:编译或不编译 | bool "Enable Feature X" |
depends on |
依赖关系 | depends on PCI |
select |
反向选择(自动开启依赖项) | select CRC32 |
help |
帮助文本 | help\n Say Y if you need... |
举个例子,一个简单的驱动 Kconfig 长这样:
config MY_SENSOR_DRIVER
tristate "My Custom Sensor Driver"
depends on I2C
select CRC8
help
This driver supports My Custom Sensor over I2C.
Say Y to compile it into the kernel.
Say M to compile it as a module.
make defconfig 生成默认配置,然后在此基础上做减法。别从零开始配,那太容易漏东西了。
3.2 Makefile:内核的「构建脚本」
Kconfig 决定了「要不要编译」,Makefile 决定了「怎么编译」。每个驱动目录下都有一个 Makefile,里面用 obj-$(CONFIG_XXX) 的语法来控制编译行为。
看个例子:
# drivers/iio/imu/Makefile
obj-$(CONFIG_BMI160) += bmi160_core.o
obj-$(CONFIG_BMI160_I2C) += bmi160_i2c.o
obj-$(CONFIG_BMI160_SPI) += bmi160_spi.o
当 CONFIG_BMI160=y 时,bmi160_core.o 会被编译并链接进内核。如果是 =m,则编译成独立的 .ko 模块文件。
这里有个坑——我曾经遇到过:
=m,但忘了把它的依赖模块也设成 =m 或 =y。结果编译出来的内核模块加载时报「Unknown symbol」,查了半天才发现是依赖没跟上。所以,depends on 和 select 一定要仔细检查。
3.3 裁剪内核模块:从大到小做减法
裁剪不是一上来就删东西,而是先搞清楚「哪些是必须的」。我一般按这个流程走:
- 确定硬件清单——CPU 架构、外设接口、存储设备、网络芯片,列个表。
- 关闭无关架构——比如你跑 ARM,就把 x86、MIPS、RISC-V 全关掉。
- 关闭无关驱动——声卡、显卡、电视调谐器、蓝牙……用不到的统统关掉。
- 关闭调试选项——
DEBUG_KERNEL、DEBUG_FS、DYNAMIC_DEBUG这些在正式产品里必须关。 - 关闭文件系统——只保留你需要的,比如 ext4、squashfs,其他的全关。
- 关闭网络协议——如果不需要 IPv6、802.11、蓝牙协议栈,就关掉。
下面这张图是我自己总结的裁剪决策流程:
3.4 编译优化选项:让内核跑得更快更小
配置裁剪完了,编译选项还能再压一压。我常用的优化手段有这几个:
- 使用
-Os优化——针对尺寸优化,比-O2能小 10%~20%。代价是性能略有下降,但对嵌入式设备来说,尺寸往往比那点性能更重要。 - 开启
CC_OPTIMIZE_FOR_SIZE——内核配置里就有这个选项,勾上就行。 - 去掉内核符号表——
CONFIG_KALLSYMS=n,能省不少空间。不过调试时建议开着,正式发布再关。 - 使用 LTO(链接时优化)——内核 5.x 以后支持
CONFIG_LTO_CLANG,能跨文件优化,进一步减小体积。 - 去掉打印信息——
CONFIG_PRINTK=n或降低日志级别,能省掉大量字符串常量。
-Os 编译。
3.5 避坑指南:我踩过的几个雷
裁剪这事儿,说起来简单,做起来全是细节。我把自己踩过的坑列出来,你遇到了能少走弯路:
- 别把 initramfs 裁没了——有一次我把
CONFIG_BLK_DEV_INITRD关了,结果内核启动到一半就 panic,因为找不到根文件系统。 - 模块依赖要理清——比如你用了
CONFIG_SOUND,但忘了开CONFIG_SND,编译直接报错。用make olddefconfig可以自动补全依赖。 - 别在裁剪后直接部署——先拿 QEMU 或开发板跑一遍,确认所有外设都能正常工作。我吃过一次亏,裁剪后网卡驱动没编进去,板子成了「孤儿」。
- 保留一份原始配置——每次裁剪前,把
.config备份一下。改乱了还能回退。
3.6 小结
内核裁剪的核心就三件事:Kconfig 决定要不要、Makefile 决定怎么编、编译选项决定多快多小。这三者配合好了,你就能得到一个既小又稳的内核。
嗯,这一章的内容就到这儿。记住,裁剪不是一锤子买卖,而是一个迭代优化的过程。每改一次配置,编译一次,验证一次,慢慢你就会找到最适合你硬件的那套配置。