4. 内核配置与裁剪:让Linux内核轻装上阵

嵌入式系统资源有限,Flash和RAM都是按MB甚至KB来算的。一个完整的内核动辄几十MB,直接烧进去?不现实。所以,内核配置与裁剪是每个嵌入式Linux工程师的必修课。说白了,就是去掉你不需要的,只留下硬件必须的。

我个人习惯,拿到一块新板子,第一件事不是写驱动,而是先做内核裁剪。把基础跑通了,再慢慢加功能。这章我们就聊聊怎么把内核“瘦身”成功。

4.1 Kconfig机制:内核配置的“语法书”

Kconfig是啥?它是内核配置系统的“语法”。每个驱动、每个子系统,都有一个Kconfig文件,定义了它的开关、依赖和帮助信息。你运行make menuconfig时看到的界面,就是Kconfig解析出来的。

一个典型的Kconfig条目长这样:

config CONFIG_DRIVER_NAME
    tristate "My Driver Support"
    depends on ARM
    select CONFIG_ANOTHER_DRIVER
    help
      This driver supports my custom hardware.
      Say Y here if you need this driver.

这里有几个关键点:

  • tristate:三态,Y(编译进内核)、M(编译为模块)、N(不编译)。嵌入式里我建议能用Y就用Y,模块加载有时会引入延迟。
  • depends on:依赖关系。比如你的驱动依赖ARM架构,那x86上就看不到这个选项。
  • select:反向依赖。选中这个选项,会自动选中另一个。小心循环依赖,我踩过这个坑。
小技巧:用make savedefconfig可以生成最小配置。它会只保留你修改过的选项,去掉所有默认值。这个文件特别适合版本管理。

4.2 基于硬件平台的模块化裁剪

裁剪不是瞎删。你得知道硬件上有什么,没什么。我见过有人把串口驱动删了,结果控制台没了,板子变砖。嗯,这里要注意:先列硬件清单,再动手裁剪。

我的做法是三步走:

  1. 列出硬件外设:CPU型号、内存大小、存储类型、网卡、串口、I2C、SPI、USB、GPIO等。
  2. 对照内核选项:每个外设对应哪些驱动,找到Kconfig里的位置。
  3. 关闭无关项:比如你的板子没有WiFi,那把无线网络子系统整个关掉。

下面这张图展示了裁剪的核心逻辑:

内核模块化裁剪流程 硬件外设清单 CPU/内存/存储/外设 Kconfig选项映射 驱动/子系统/依赖关系 裁剪决策 保留/关闭/模块化 编译验证 功能测试/尺寸检查 回退 ⚠ 裁剪原则 • 保留启动必需驱动:串口、存储、时钟、中断控制器 • 关闭调试选项:DEBUG_FS、DEBUG_KERNEL、PRINTK_TIME • 模块化非关键驱动:音频、视频、网络协议栈

4.3 内核尺寸优化:去掉无用驱动和子系统

裁剪的核心目标就是减小内核镜像体积。我做过一个项目,裁剪前内核是8MB,裁剪后只有1.2MB。怎么做到的?一个字:删。

下面这张表列出了常见的可裁剪子系统:

子系统 典型尺寸 裁剪条件 备注
无线网络 (CFG80211/MAC80211) ~500KB 无WiFi/蓝牙模块 直接关闭
USB子系统 ~300KB 无USB接口 保留HID可能有用
音频子系统 (ALSA) ~400KB 无音频编解码器 可模块化
视频/V4L2 ~350KB 无摄像头/显示 直接关闭
文件系统 (EXT4/XFS/BTRFS) ~200KB/个 只用一种文件系统 保留需要的即可
网络协议栈 (IPv6/IPX) ~150KB 只用IPv4 关闭IPv6
调试选项 (DEBUG_FS/PRINTK) ~100KB 生产环境 必须关闭
避坑指南:我曾经为了省空间,把EXT4文件系统关了,结果根文件系统挂载不上。后来才发现内核必须支持根文件系统所在的分区类型。所以,根文件系统驱动必须编译进内核,不能模块化。

4.4 内核编译优化:LTO和优化等级

裁剪完配置,接下来就是编译优化。这里有两个关键点:优化等级和LTO(链接时优化)。

4.4.1 优化等级的选择

内核默认用-O2。你可以改成-Os(优化尺寸)或-O3(优化性能)。怎么改?在Makefile里找到KBUILD_CFLAGS,或者直接在menuconfig里设置:

# 在menuconfig中
General setup → 
    Compiler optimization level (Optimize for size (-Os))

我个人建议:生产环境用-Os。为什么?因为嵌入式设备对尺寸敏感,-Os能减少10%-20%的代码体积。代价是性能略有下降,但通常感觉不到。

三种优化等级的对比:

优化等级 内核尺寸 性能 适用场景
-O2 基准 基准 默认,开发调试
-Os 减少10-20% 略降1-3% 生产环境,Flash紧张
-O3 增加5-10% 提升3-5% 性能敏感,Flash充足

4.4.2 LTO:链接时优化

LTO是GCC的一个特性,它能在链接阶段做全局优化。说白了,就是让编译器看到整个程序,而不是单个文件。这样能发现更多优化机会。

启用LTO的方法:

# 在menuconfig中
General setup → 
    Enable Link Time Optimization (LTO) (EXPERIMENTAL)

启用后,编译时间会变长(大约1.5-2倍),但内核尺寸能再减小5-10%。我试过一个项目,LTO后内核从1.2MB降到了1.1MB,虽然不多,但聊胜于无。

经验之谈:LTO在GCC 8.0以上版本比较稳定。如果你用的是老版本GCC,建议先测试一下。我曾经在GCC 4.9上开LTO,结果内核启动就panic。嗯,血的教训。

4.5 实战:一个完整的裁剪案例

假设我们有一个ARM Cortex-A7的板子,256MB RAM,128MB NAND Flash,只有串口、网口、I2C和GPIO。没有WiFi、没有USB、没有音频。

裁剪步骤:

  1. 关闭无线网络:Networking support → Wireless → [ ]
  2. 关闭USB:Device Drivers → USB support → [ ]
  3. 关闭音频:Device Drivers → Sound card support → [ ]
  4. 关闭文件系统:只保留EXT4和proc、sysfs,其他全关。
  5. 关闭调试:Kernel hacking → [ ] Debug Filesystem,[ ] Kernel debugging
  6. 设置优化等级:-Os
  7. 启用LTO:如果GCC版本支持。

裁剪前后对比:

指标 裁剪前 裁剪后 优化比例
内核镜像尺寸 6.8 MB 1.5 MB 78%
编译时间 12分钟 8分钟 33%
启动时间 8秒 3秒 62.5%
内存占用 64 MB 32 MB 50%

你看,裁剪的效果非常明显。内核小了,启动快了,内存也省了。这就是嵌入式Linux优化的魅力。

最后提醒一句:裁剪完一定要做回归测试。特别是那些依赖关系复杂的子系统,比如网络协议栈。我曾经关了一个看似无关的选项,结果网卡驱动编译不过。所以,每改一个配置,就编译一次,测试一次。别攒一堆问题再排查,那会让人崩溃。