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 模块裁剪策略

模块的好处是按需加载,不占内核镜像空间。但坏处是多了也会增加文件系统的体积。我的做法是:

  1. 先用 make menuconfig 把确定不用的设备设为 [ ]
  2. 把不确定的设备设为 [M]
  3. 编译后启动系统,用 lsmod 查看实际加载了哪些模块
  4. 把没加载过的模块从配置里彻底移除

避坑指南: 我曾经在裁剪网络驱动时,把 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 内核裁剪流程总览

下面这张图是我自己总结的裁剪流程,每次做新项目我都会照着走一遍:

内核裁剪流程 内核裁剪标准流程 列出硬件清单 make defconfig 生成基础配置 make menuconfig 关闭无用驱动 设置 -Os / LTO / 去调试信息 编译 & 启动测试 lsmod 验证 & 迭代优化 关键原则 • 先砍确定不用的 • 不确定的改成模块 • 每次修改都备份 .config

这个流程我用了好几年,基本没翻过车。核心思路就是:先粗后细,先大后小,每次只改一个方向,改完就编译测试。

4.5 常见问题与解决

  • 裁剪后内核无法启动: 八成是关了必要的驱动(如串口、存储控制器)。用 make oldconfig 恢复默认配置,再逐步裁剪。
  • 模块加载失败: 检查 depmod 是否执行,或者模块之间有依赖没满足。用 modinfo 查看模块依赖。
  • 编译报错: 某些配置项之间有互斥关系。比如开启了 CONFIG_PREEMPT 就不能同时开 CONFIG_PREEMPT_RT。看报错信息里的 depends on 提示。

最后提醒一句: 裁剪不是一锤子买卖。项目迭代过程中,硬件可能会换,外设可能会加,内核配置也要跟着调整。我习惯把 .config 文件纳入版本管理,每次修改都写清楚原因。这样半年后回头看,还能知道当时为什么砍掉某个驱动。