banner
NEWS LETTER

嵌入式 Linux 内核裁剪与定制

Scroll down

嵌入式 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
2
3
4
5
# 如果使用厂商 BSP,通常在 arch/<arch>/configs/ 下有 defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- <board>_defconfig

# 或者从当前运行内核导出
zcat /proc/config.gz > .config

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
2
3
# 生成最小化 defconfig(只包含非默认值的选项)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- savedefconfig
cp defconfig arch/arm/configs/<board>_defconfig

这样可以确保后续每次构建都基于干净的配置,不会混入调试残留。

4. 检查模块依赖

1
2
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- modules_prepare
# 确认没有 missing symbol 警告

5. 构建并对比

1
2
3
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j$(nproc)
# 对比裁剪前后镜像大小
ls -lh arch/arm/boot/zImage

6. 在目标板上验证

1
2
3
4
# 烧录后检查启动日志,确认所有必要驱动正常加载
dmesg | grep -i "fail\|error\|missing"
# 检查设备节点是否齐全
ls /dev/

四、常见坑

  • 依赖遗漏:关闭一个顶层选项可能隐式关闭多个依赖项。例如关闭 CONFIG_NET 会同时移除所有网络协议和套接字支持,即使你需要本地回环。使用 make menuconfig 的搜索功能(按 /)查看依赖关系。
  • 模块 vs built-in 误判:根文件系统所在的存储控制器驱动必须 built-in,否则内核无法挂载 rootfs。initramfs 场景下也需要把相关驱动编入内核。
  • 设备树与内核配置不同步:设备树中引用了某个外设节点,但内核配置中关闭了对应驱动,不会报错但功能静默失效。务必对照设备树节点逐个检查内核配置。
  • OTA 升级兼容性:裁剪后的内核镜像如果大小变化显著,可能需要更新 U-Boot 的 bootm 参数或分区表,否则加载地址冲突导致启动失败。
  • 调试配置残留:从开发配置切换到量产配置时,CONFIG_DEBUG_INFOCONFIG_PRINTK 的等级、CONFIG_SYSCTL 等选项容易遗漏。建议维护两份 defconfig(debug 和 release),不要在同一个配置上反复开关。
  • 交叉编译工具链版本:裁剪过程中如果同时升级了 GCC 版本,可能引入新的编译警告或行为变化。建议先固定工具链版本,再做配置调整。

五、检查清单

  • 列出目标板所有硬件外设及对应的内核驱动选项
  • 确认 rootfs 存储控制器驱动已编入内核(built-in)
  • 关闭所有 CONFIG_DEBUG_* 选项
  • 关闭 CONFIG_KASANCONFIG_KMEMLEAKCONFIG_FTRACE 等运行时调试设施
  • 只保留产品实际使用的文件系统支持
  • 只保留产品实际使用的网络协议和驱动
  • 运行 savedefconfig 并确认 defconfig 干净
  • 在目标板上验证所有设备节点存在且功能正常
  • 对比裁剪前后内核镜像大小,记录变化
  • 确认 OTA 升级流程中分区表和加载参数与新镜像匹配

六、小结

内核裁剪是嵌入式 Linux 从开发板走向量产产品的必经步骤。核心在于”理解硬件需求,只留必要功能”,而非追求极端的体积最小化。一份干净的 defconfig 不仅减小镜像体积,也让系统行为更可预测、攻击面更小、维护成本更低。建议将裁剪后的 defconfig 纳入版本管理,与 BSP 代码一起维护,确保每次构建都可复现。

其他文章
目录导航 置顶
  1. 1. 一、问题背景
  2. 2. 二、核心思路
  3. 3. 三、落地步骤
    1. 3.1. 1. 获取基线配置
    2. 3.2. 2. 开启菜单配置
    3. 3.3. 3. 利用 make savedefconfig 精简
    4. 3.4. 4. 检查模块依赖
    5. 3.5. 5. 构建并对比
    6. 3.6. 6. 在目标板上验证
  4. 4. 四、常见坑
  5. 5. 五、检查清单
  6. 6. 六、小结
请输入关键词进行搜索