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文件存在,按需加载。镜像小,灵活,但启动时多一步加载过程。
怎么选?我有个简单的原则:
- 必须用的、启动就要用的:比如根文件系统驱动、串口驱动、中断控制器驱动,必须静态编译。
- 偶尔用的、热插拔的:比如USB设备驱动、Wi-Fi驱动,做成模块。
- 调试用的、不常用的:比如某些测试驱动、不常用的网卡驱动,做成模块,甚至不编译。
我曾经在一个项目中,把所有驱动都静态编译了。结果内核镜像从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 第六步:验证与测试
裁剪完一定要测试。我见过有人裁剪得太狠,把某个关键驱动删了,结果系统起不来。我的做法是:
- 先编译一个最小配置,确保能启动
- 然后逐步添加需要的功能,每次加一点就编译测试一次
- 最后做压力测试,确保稳定性
小技巧:
可以用make savedefconfig生成精简的配置文件。这个文件只包含你修改过的选项,方便版本管理。
好了,内核配置与裁剪就讲到这里。记住一句话:内核不是越大越好,而是越精越好。下一章我们会聊聊内核启动优化,到时候再细说怎么让系统秒开。