3、内核配置与裁剪:内核配置选项对性能的影响、模块化与静态编译的选择、内核裁剪实战

说到嵌入式Linux的性能优化,内核配置与裁剪是绕不开的第一道坎。我见过太多团队,硬件选型花了几个月,结果内核直接拿个通用配置一编就烧进去了。嗯,这样跑起来,性能能好才怪。

说白了,内核就是操作系统的核心。它的大小、功能集、调度方式,直接决定了你的系统能跑多快、占多少内存。今天我们就来聊聊,怎么通过配置和裁剪,把内核从「万能瑞士军刀」变成「专为你的硬件定制的手术刀」。

3.1 内核配置选项对性能的影响

内核配置选项多如牛毛,但真正影响性能的,其实就那么几类。我习惯把它们分成三块:调度器、内存管理、以及驱动子系统。

3.1.1 调度器选择

调度器决定了CPU怎么分配时间片。不同的场景,选错了调度器,性能差距能到30%以上。

调度器类型 适用场景 性能特点
CFS(完全公平调度) 通用桌面、服务器 公平性好,延迟均衡
RT(实时调度) 工业控制、音视频 低延迟,确定性高
BFS/MuQSS 桌面交互、轻负载 响应快,但多核扩展性差

我在一个工业相机项目中,一开始用的CFS,结果发现图像采集线程总是被其他进程打断,导致帧率不稳定。后来切到RT调度,配合优先级设置,问题就解决了。你想想看,如果选错了调度器,硬件再强也白搭。

关键配置项:

  • CONFIG_PREEMPT:抢占式内核,提升响应速度,但增加上下文切换开销
  • CONFIG_PREEMPT_RT:完全实时抢占,适合硬实时场景
  • CONFIG_HZ:时钟中断频率,100Hz省电,1000Hz响应快

3.1.2 内存管理选项

内存管理这块,我建议重点关注两个:页面大小和内存压缩。

页面大小默认是4KB,但如果你跑的是大数据或数据库应用,可以试试64KB甚至1MB的大页。大页能减少TLB miss,提升内存访问速度。不过代价是内存碎片更严重,小内存分配会浪费空间。

内存压缩(zram/zswap)在内存紧张时很有用。我记得有个物联网项目,设备只有64MB RAM,跑个Web服务器都费劲。开了zram后,压缩率大概2:1,相当于凭空多出30MB可用内存。当然,压缩解压会消耗CPU,你要权衡一下。

我的建议:

如果设备内存小于128MB,一定要开内存压缩。如果内存大于512MB,关掉它,省下CPU给业务用。

3.1.3 驱动子系统配置

驱动这块,很多人容易忽略。你以为只是「能用就行」,其实驱动配置对性能影响很大。

比如网络驱动,NAPI(New API)和中断合并(Interrupt Coalescing)这两个选项,直接决定了网卡的吞吐量。NAPI能减少中断次数,在高吞吐场景下,CPU占用能降低40%。

再比如存储驱动,IO调度器的选择。CFQ适合机械硬盘,Deadline适合SSD,NOOP适合NVMe或虚拟化环境。选错了,IO延迟能差好几倍。

注意:

不要一股脑把所有驱动都编进内核。只保留你硬件需要的驱动,其他的统统关掉。否则内核启动时,会花大量时间枚举不存在的设备。

3.2 模块化与静态编译的选择

这是个老生常谈的问题,但很多人还是搞不清楚。我直接说结论:

  • 静态编译(built-in):驱动直接编进内核镜像。启动快,没有加载延迟,但镜像体积大。
  • 模块化(module):驱动以.ko文件存在,按需加载。镜像小,灵活,但启动时多一步加载过程。

怎么选?我有个简单的原则:

  1. 必须用的、启动就要用的:比如根文件系统驱动、串口驱动、中断控制器驱动,必须静态编译。
  2. 偶尔用的、热插拔的:比如USB设备驱动、Wi-Fi驱动,做成模块。
  3. 调试用的、不常用的:比如某些测试驱动、不常用的网卡驱动,做成模块,甚至不编译。

我曾经在一个项目中,把所有驱动都静态编译了。结果内核镜像从2MB膨胀到8MB,Flash空间不够,还得换芯片。后来改成模块化,镜像降到2.5MB,启动时按需加载,性能几乎没有损失。

性能对比:

方式 启动时间 镜像大小 内存占用 灵活性
全静态编译 快(无加载延迟) 高(所有驱动常驻)
全模块化 慢(需加载模块) 低(按需加载)
混合模式 适中 适中 适中

3.3 内核裁剪实战

好了,理论说完了,我们来点实际的。下面是我做内核裁剪的步骤,你可以直接照着做。

3.3.1 第一步:分析硬件需求

先搞清楚你的硬件有哪些外设。比如:

  • CPU架构:ARM、MIPS、RISC-V?
  • 存储:NAND、eMMC、SD卡?
  • 网络:有线、Wi-Fi、蓝牙?
  • 外设:I2C、SPI、UART、GPIO?

把这些列个清单,然后去内核配置里,只保留对应的驱动。其他的,统统关掉。

3.3.2 第二步:使用make menuconfig

运行make menuconfig,进入图形化配置界面。我习惯先加载一个基础配置(比如defconfig),然后逐项裁剪。

# 加载默认配置
make ARCH=arm defconfig

# 进入图形化配置
make ARCH=arm menuconfig

在菜单里,按/可以搜索配置项。比如搜索CONFIG_USB,就能找到所有USB相关的选项。

3.3.3 第三步:裁剪文件系统支持

文件系统这块,很多人会踩坑。我建议只保留你需要的文件系统类型:

  • 如果根文件系统是ext4,就只保留ext4支持
  • 如果用的是SquashFS(只读压缩文件系统),就只保留它
  • 其他如btrfs、xfs、ntfs,统统关掉

我曾经帮一个客户排查问题,发现他的内核编译了20多种文件系统支持,但实际只用了一种。裁剪掉之后,内核镜像小了30%,启动时间快了15%。

3.3.4 第四步:关闭调试选项

调试选项是性能杀手。很多开发者在开发阶段开了各种调试,发布时忘了关。常见的要关掉的选项:

CONFIG_DEBUG_KERNEL=n
CONFIG_DEBUG_INFO=n
CONFIG_DYNAMIC_DEBUG=n
CONFIG_PRINTK_TIME=n
CONFIG_SCHED_DEBUG=n

这些选项开了之后,内核会多出很多日志输出和检查逻辑。关掉它们,性能能提升5%-10%。

注意:

关掉调试选项前,确保你的系统已经稳定。否则出了问题,没有日志可查,那就麻烦了。

3.3.5 第五步:使用内核大小优化选项

内核配置里有一些专门优化大小的选项:

  • CONFIG_CC_OPTIMIZE_FOR_SIZE:让编译器优化代码大小,而不是速度
  • CONFIG_KALLSYMS=n:关掉符号表,能省不少空间
  • CONFIG_MODULE_UNLOAD=n:如果不卸载模块,关掉这个功能

我习惯在发布版本里开启CC_OPTIMIZE_FOR_SIZE,虽然性能会损失一点点(大概1%-2%),但镜像能小20%以上。对于Flash空间紧张的项目,这点牺牲是值得的。

3.3.6 第六步:验证与测试

裁剪完一定要测试。我见过有人裁剪得太狠,把某个关键驱动删了,结果系统起不来。我的做法是:

  1. 先编译一个最小配置,确保能启动
  2. 然后逐步添加需要的功能,每次加一点就编译测试一次
  3. 最后做压力测试,确保稳定性

小技巧:

可以用make savedefconfig生成精简的配置文件。这个文件只包含你修改过的选项,方便版本管理。

好了,内核配置与裁剪就讲到这里。记住一句话:内核不是越大越好,而是越精越好。下一章我们会聊聊内核启动优化,到时候再细说怎么让系统秒开。