嵌入式 Linux 系统的内核镜像大小直接影响启动时间、存储占用和运行时内存余量。一个未经裁剪的通用内核动辄数十 MB,而多数嵌入式设备只需要其中很小一部分功能。内核裁剪就是把不需要的驱动、文件系统、网络协议和调试设施从内核中剔除,最终得到一个功能完备且体积紧凑的定制内核。
内核定制并非简单地关闭选项。它要求开发者对硬件外设、BSP 提供的能力、产品功能边界有清晰认知。裁剪过头会导致缺少必要驱动或模块依赖断裂,裁剪不足则浪费存储和内存。本文给出一套可操作的裁剪流程和常见陷阱,帮助开发者在功能完整性和镜像体积之间取得平衡。
一、问题背景
在资源受限的嵌入式平台上——例如 64MB RAM 的 IoT 网关、8MB Flash 的工业控制器、或需要快速启动的车载终端——内核体积是系统设计的硬约束。过大的内核不仅占用宝贵的 Flash 空间,还会拖慢启动速度、增加 OTA 升级的传输时间和失败风险。
同时,内核中大量未使用的代码会增加攻击面。安全审计表明,许多 CVE 影响的功能在特定产品中根本不需要。裁剪掉不需要的子系统是减少攻击面的直接手段。
另一个现实问题是 BSP 升级。厂商提供的 BSP 内核配置往往保留了大量通用选项,直接用于量产是不合适的。每个产品线都需要基于自身硬件和功能需求做一次定制裁剪。
二、核心思路
- 先加法后减法:从一个最小可启动配置开始,逐步添加产品需要的功能,比从通用配置中逐个关闭更可控。
- 分层裁剪:区分”必须编译进内核”和”可作为模块加载”两类功能。关键启动路径上的驱动应编译进内核(built-in),非关键功能用模块(module)按需加载。
- 硬件驱动先行:明确 BSP 支持的 SoC 型号,列出板上所有外设(UART、SPI、I2C、GPIO、以太网、Wi-Fi、存储控制器等),逐个确认内核选项。
- 文件系统精简:只保留产品实际使用的文件系统支持(ext4、squashfs、UBIFS 等),去掉不相关的(NFS、CIFS、FAT 等除非产品确实需要)。
- 调试选项清理:量产内核应关闭所有调试输出(
CONFIG_DEBUG_*)、KASAN、KMEMLEAK、ftrace 等。这些在开发阶段有用,但会显著增加镜像体积和运行开销。 - 保留回退能力:即使裁剪到最小,也应保留
CONFIG_KEXEC或 U-Boot 加载备用内核的能力,以便在新内核出问题时快速恢复。
三、落地步骤
1. 获取基线配置
1 | # 如果使用厂商 BSP,通常在 arch/<arch>/configs/ 下有 defconfig |
2. 开启菜单配置
1 | make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig |
在菜单中逐层检查以下关键目录:
| 目录 | 关注点 |
|---|---|
Device Drivers |
板载外设驱动,关闭无用设备类别 |
File systems |
仅保留产品使用的文件系统 |
Networking support |
关闭不需要的协议族和功能(如蓝牙、CAN 等) |
Kernel hacking |
量产时全部关闭 |
Security options |
根据安全需求配置 SELinux/AppArmor |
General setup |
关闭不需要的 IPC 机制、控制组等 |
3. 利用 make savedefconfig 精简
1 | # 生成最小化 defconfig(只包含非默认值的选项) |
这样可以确保后续每次构建都基于干净的配置,不会混入调试残留。
4. 检查模块依赖
1 | make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- modules_prepare |
5. 构建并对比
1 | make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j$(nproc) |
6. 在目标板上验证
1 | # 烧录后检查启动日志,确认所有必要驱动正常加载 |
四、常见坑
- 依赖遗漏:关闭一个顶层选项可能隐式关闭多个依赖项。例如关闭
CONFIG_NET会同时移除所有网络协议和套接字支持,即使你需要本地回环。使用make menuconfig的搜索功能(按/)查看依赖关系。 - 模块 vs built-in 误判:根文件系统所在的存储控制器驱动必须 built-in,否则内核无法挂载 rootfs。
initramfs场景下也需要把相关驱动编入内核。 - 设备树与内核配置不同步:设备树中引用了某个外设节点,但内核配置中关闭了对应驱动,不会报错但功能静默失效。务必对照设备树节点逐个检查内核配置。
- OTA 升级兼容性:裁剪后的内核镜像如果大小变化显著,可能需要更新 U-Boot 的
bootm参数或分区表,否则加载地址冲突导致启动失败。 - 调试配置残留:从开发配置切换到量产配置时,
CONFIG_DEBUG_INFO、CONFIG_PRINTK的等级、CONFIG_SYSCTL等选项容易遗漏。建议维护两份 defconfig(debug 和 release),不要在同一个配置上反复开关。 - 交叉编译工具链版本:裁剪过程中如果同时升级了 GCC 版本,可能引入新的编译警告或行为变化。建议先固定工具链版本,再做配置调整。
五、检查清单
- 列出目标板所有硬件外设及对应的内核驱动选项
- 确认 rootfs 存储控制器驱动已编入内核(built-in)
- 关闭所有
CONFIG_DEBUG_*选项 - 关闭
CONFIG_KASAN、CONFIG_KMEMLEAK、CONFIG_FTRACE等运行时调试设施 - 只保留产品实际使用的文件系统支持
- 只保留产品实际使用的网络协议和驱动
- 运行
savedefconfig并确认 defconfig 干净 - 在目标板上验证所有设备节点存在且功能正常
- 对比裁剪前后内核镜像大小,记录变化
- 确认 OTA 升级流程中分区表和加载参数与新镜像匹配
六、小结
内核裁剪是嵌入式 Linux 从开发板走向量产产品的必经步骤。核心在于”理解硬件需求,只留必要功能”,而非追求极端的体积最小化。一份干净的 defconfig 不仅减小镜像体积,也让系统行为更可预测、攻击面更小、维护成本更低。建议将裁剪后的 defconfig 纳入版本管理,与 BSP 代码一起维护,确保每次构建都可复现。
- 本文链接: https://blog.hansong.icu/2026/07/21/daily_post_2026_07_21/
- 版权声明: 本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。