4. 内核裁剪:内核配置基础、关闭无用驱动与模块、编译优化选项
内核裁剪这事儿,说白了就是给系统“瘦身”。我刚开始做嵌入式时,总觉得内核越大越稳,结果有一次项目交付前发现 Flash 空间死活差 2MB……嗯,从那以后我就把裁剪当成必修课了。
这一章我们聊三个核心点:怎么配内核、怎么砍掉没用的驱动和模块、怎么让编译出来的内核跑得更快更小。
4.1 内核配置基础
内核配置不是靠猜的。你得先搞清楚目标硬件上到底有哪些设备。我个人习惯先跑一遍 lspci 和 lsusb,把硬件清单列出来,再对着内核菜单一项项勾。
配置内核的三种方式:
make menuconfig—— 最常用,基于 ncurses 的文本菜单,适合终端环境make xconfig—— 图形化界面,依赖 Qt,适合桌面开发机make defconfig—— 用默认配置快速生成,适合先搭骨架再微调
我建议新手从 make menuconfig 入手。为什么?因为它不需要图形桌面,SSH 连上去就能用,而且搜索功能很顺手——按 / 键输入关键词,就能找到对应的配置项。
小技巧: 配置项前面的 [*] 表示编译进内核,[M] 表示编译成模块,[ ] 表示不编译。裁剪时优先把不用的设备从 [*] 改成 [ ],实在拿不准的改成 [M]。
4.2 关闭无用驱动与模块
这是裁剪的大头。你想想看,一个通用发行版内核里可能包含上千个驱动,但你的板子上可能只用到了十几个。剩下的全是负担。
我曾经接手过一个项目,内核镜像 12MB,启动后内存占用 80MB。后来一查,光显卡驱动就占了 3MB,而那块板子根本没有显示接口……
4.2.1 驱动裁剪清单
| 驱动类别 | 常见可裁剪项 | 裁剪后收益 |
|---|---|---|
| 显卡/显示 | DRM、FrameBuffer、GPU 驱动 | 减少 2~5MB |
| 音频 | ALSA、HDA、USB 音频 | 减少 1~3MB |
| 网络 | 无线网卡、蓝牙、PPP/PPPoE | 减少 1~4MB |
| 存储 | RAID、SAS、老旧 IDE 驱动 | 减少 1~2MB |
| 输入 | 触摸屏、游戏手柄、Wacom 数位板 | 减少 0.5~1MB |
| 虚拟化 | KVM、Xen、VirtualBox 驱动 | 减少 1~3MB |
注意: 别一股脑全关了。我见过有人把串口驱动也关了,结果板子起不来,连调试信息都看不到。建议每次修改后保存一份 .config 备份,方便回退。
4.2.2 模块裁剪策略
模块的好处是按需加载,不占内核镜像空间。但坏处是多了也会增加文件系统的体积。我的做法是:
- 先用
make menuconfig把确定不用的设备设为[ ] - 把不确定的设备设为
[M] - 编译后启动系统,用
lsmod查看实际加载了哪些模块 - 把没加载过的模块从配置里彻底移除
避坑指南: 我曾经在裁剪网络驱动时,把 CONFIG_NET_VENDOR_INTEL 关了,结果板子上的 Intel 网卡不工作。后来才发现这个选项是“厂商开关”,关了会把该厂商所有网卡驱动都屏蔽掉。所以裁剪时一定要看清楚依赖关系。
4.3 编译优化选项
配置搞定了,接下来就是让编译器帮我们压榨性能。这里我重点说三个方向:
4.3.1 编译器优化级别
内核默认用 -O2,这个级别兼顾了性能和体积。如果你对体积有极致要求,可以试试 -Os(优化体积)。
# 在 Makefile 或内核配置中设置
CONFIG_CC_OPTIMIZE_FOR_SIZE=y # 启用 -Os 优化
CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE=n # 关闭 -O2
我实测过,从 -O2 切到 -Os,内核镜像能缩小 10%~15%,但某些计算密集型的驱动性能会下降 5% 左右。如果你的系统对实时性要求高,建议保留 -O2。
4.3.2 链接时优化(LTO)
LTO 是 GCC 的一个特性,它能在链接阶段做跨文件的优化。内核从 5.x 开始支持 LTO,开启后能进一步减小体积。
CONFIG_LTO=y
CONFIG_LTO_CLANG=y # 如果用 Clang 编译
注意: LTO 会显著增加编译时间(我试过,从 10 分钟变成 40 分钟)。建议只在最终发布版本开启,开发调试阶段关掉。
4.3.3 去掉调试信息
这是最直接的瘦身手段。内核默认会带很多调试符号,去掉后镜像能小 30%~50%。
# 关闭内核调试信息
CONFIG_DEBUG_INFO=n
CONFIG_DEBUG_KERNEL=n
# 如果必须保留调试,可以只保留压缩格式
CONFIG_DEBUG_INFO_COMPRESSED=y
我曾经在一个存储空间只有 8MB 的板子上做裁剪,去掉调试信息后,内核从 6.2MB 降到了 3.8MB,直接省出了应用层的空间。
4.4 内核裁剪流程总览
下面这张图是我自己总结的裁剪流程,每次做新项目我都会照着走一遍:
这个流程我用了好几年,基本没翻过车。核心思路就是:先粗后细,先大后小,每次只改一个方向,改完就编译测试。
4.5 常见问题与解决
- 裁剪后内核无法启动: 八成是关了必要的驱动(如串口、存储控制器)。用
make oldconfig恢复默认配置,再逐步裁剪。 - 模块加载失败: 检查
depmod是否执行,或者模块之间有依赖没满足。用modinfo查看模块依赖。 - 编译报错: 某些配置项之间有互斥关系。比如开启了
CONFIG_PREEMPT就不能同时开CONFIG_PREEMPT_RT。看报错信息里的depends on提示。
最后提醒一句: 裁剪不是一锤子买卖。项目迭代过程中,硬件可能会换,外设可能会加,内核配置也要跟着调整。我习惯把 .config 文件纳入版本管理,每次修改都写清楚原因。这样半年后回头看,还能知道当时为什么砍掉某个驱动。