4、内核裁剪方法论:裁剪原则与策略、功能模块裁剪、文件系统裁剪、驱动裁剪
内核裁剪这事儿,说白了就是做减法。我刚开始接触Tina Linux时,总想把所有功能都塞进去,结果编译出来的内核镜像快赶上我半个硬盘了。后来被产品经理追着要「启动速度再快2秒」,才真正开始研究怎么科学地做裁剪。
你想想看,一个完整的内核源码树里,真正跑起来能用到的代码可能不到20%。剩下的那些,要么是你不用的硬件驱动,要么是永远用不上的网络协议栈。裁剪的目标,就是把这80%的「脂肪」减掉,只留下能跑业务的「肌肉」。
核心原则:裁剪不是删代码,而是配置管理。内核源码不动,只通过Kconfig和Makefile控制哪些代码被编译进去。
4.1 裁剪原则与策略
先说说原则。我个人的经验是,裁剪之前先问自己三个问题:这个功能真的需要吗?能不能用模块代替?去掉之后影响面有多大?
三个核心原则:
- 最小化原则:只编译当前硬件平台必须的功能。比如你的板子只有以太网,就别把Wi-Fi驱动编进去。
- 模块化原则:能编成模块的就别编进内核。模块可以按需加载,启动时少占内存。
- 可追溯原则:每次裁剪都做记录。我曾经因为删了一个看起来「没用」的驱动,结果整个USB子系统罢工了,查了两天才找到原因。
我的习惯:每次裁剪前,先用make savedefconfig生成当前配置的快照。裁剪出问题后,对比两个defconfig文件,一眼就能看出改了哪里。
裁剪策略上,我建议分四步走:
- 硬件摸底:列出板子上所有外设的型号和接口类型。这一步别偷懒,我见过有人凭印象裁剪,结果漏掉了板载音频Codec,产品量产了才发现没声音。
- 功能分级:把内核功能分成「必须」「可选」「不需要」三级。必须的包括CPU架构、内存管理、中断控制器;可选的比如网络协议栈、文件系统;不需要的就是那些你永远用不上的。
- 逐层裁剪:从顶层功能往下走。先关掉整个子系统(比如
CONFIG_NET),再细化到子功能(比如CONFIG_IPV6)。 - 迭代验证:每裁剪一轮,编译一次,跑一遍基本功能测试。别想着一次到位,我试过一口气关了50个选项,结果编译都过不了。
4.2 功能模块裁剪
功能模块裁剪是重头戏。Tina Linux基于Linux内核,Kconfig里几千个选项,你得知道哪些能关、哪些不能动。
常见的可裁剪模块:
| 模块 | 裁剪建议 | 典型节省空间 |
|---|---|---|
| 网络协议栈 | 只用IPv4就关掉IPv6 | ~200KB |
| 文件系统 | 只保留ext4和squashfs | ~150KB |
| 调度器 | 单核CPU用CFS就够了 | ~50KB |
| 电源管理 | 没有电池的设备关掉ACPI | ~100KB |
| 安全模块 | 关掉SELinux/AppArmor | ~80KB |
举个例子,我在一个IoT网关项目里,只需要基本的TCP/IP通信。我把CONFIG_IPV6、CONFIG_BRIDGE、CONFIG_NETFILTER全关了,光网络子系统就省了将近500KB。
注意:关掉CONFIG_SYSFS或CONFIG_PROC_FS要谨慎。很多用户态工具依赖这两个虚拟文件系统,关了可能导致系统无法正常启动。
实际操作步骤:
# 1. 进入内核配置界面
make menuconfig
# 2. 关闭不需要的网络功能
Networking support → Networking options →
[ ] IPv6 protocol
[ ] 802.1d Ethernet Bridging
[ ] Network packet filtering
# 3. 精简文件系统支持
File systems →
[ ] Second extended fs
[ ] Ext3 journalling file system
[*] Ext4 journalling file system
[ ] Btrfs
[ ] XFS
[*] SquashFS
# 4. 保存配置
make savedefconfig
4.3 文件系统裁剪
文件系统裁剪分两层:内核里的文件系统驱动,和根文件系统本身的内容。
内核层:只保留你实际用的文件系统类型。嵌入式设备常用SquashFS做只读根文件系统,配合overlayfs做可写层。我一般只保留ext4、squashfs和overlayfs,其他全关。
根文件系统层:这才是大头。一个完整的Linux根文件系统动辄几百MB,但嵌入式设备可能只需要几MB。
我常用的裁剪手段:
- 用busybox代替coreutils:busybox一个二进制文件就能提供ls、cp、mv等几十个命令,比分开的coreutils省了至少5MB。
- 删掉man pages和文档:产品设备上谁看文档?删掉能省1-2MB。
- 只保留必要的库文件:用
ldd检查每个二进制依赖哪些动态库,没用的库删掉。 - 使用initramfs:把根文件系统压缩后跟内核打包在一起,启动时解压到内存里,省掉挂载块设备的步骤。
避坑指南:我曾经为了省空间,把/lib/modules里的内核模块全删了。结果插上一个USB设备时,系统报modprobe: module not found。后来我学乖了,至少保留跟当前硬件相关的模块。
一个精简的根文件系统结构:
/
├── bin/ # busybox + 几个关键工具
│ ├── busybox
│ ├── sh -> busybox
│ └── init -> busybox
├── sbin/ # 系统管理工具
│ ├── init
│ ├── mount
│ └── ifconfig
├── etc/ # 配置文件
│ ├── inittab
│ ├── fstab
│ └── init.d/
├── lib/ # 动态库
│ ├── libc.so.6
│ ├── ld-linux.so.3
│ └── modules/ # 内核模块(按需保留)
├── dev/ # 设备节点
├── proc/ # 挂载点
├── sys/ # 挂载点
└── tmp/ # 临时文件
这个结构大概2-3MB,启动时间能控制在3秒以内。
4.4 驱动裁剪
驱动裁剪是最容易出问题的地方。为什么?因为驱动跟硬件绑定,你删掉一个驱动,可能某个外设就彻底不工作了。
驱动裁剪的原则:
- 只保留板载外设驱动:你的板子上有什么芯片,就留什么驱动。比如用RTL8188 WiFi芯片,就把其他WiFi驱动全关掉。
- 关掉未使用的总线驱动:如果板子没有PCIe接口,关掉
CONFIG_PCI。没有USB Host,关掉CONFIG_USB。 - 驱动编成模块:不常用的驱动编成模块,启动时不加载,需要时再insmod。
我曾经踩过的坑:在一个项目里,我把CONFIG_I2C关掉了,因为觉得板子上没有I2C设备。结果系统启动到一半就挂了——原来板载的PMIC(电源管理芯片)是通过I2C通信的。从那以后,我每次裁剪驱动前都会先看一遍原理图。
驱动裁剪的检查清单:
| 驱动类型 | 检查项 | 常见陷阱 |
|---|---|---|
| 字符设备 | 串口、GPIO、SPI、I2C | PMIC、传感器可能依赖I2C |
| 块设备 | MMC/SD、NAND Flash、SATA | 根文件系统挂载需要对应驱动 |
| 网络设备 | 以太网PHY、WiFi、蓝牙 | PHY驱动常被忽略 |
| 输入设备 | 触摸屏、按键、鼠标 | 触摸屏需要input子系统 |
| 多媒体 | 音频Codec、摄像头、显示 | V4L2、ALSA框架不能少 |
实际操作:
# 查看当前加载的驱动
lsmod
# 查看硬件设备占用的驱动
cat /proc/devices
cat /proc/bus/input/devices
# 在menuconfig中搜索驱动
make menuconfig
# 按 / 键搜索,比如搜索 "rtl8188"
嗯,这里要注意:驱动裁剪完一定要做功能测试。我习惯写一个测试脚本,把板子上所有外设都跑一遍。比如测试串口就发一段数据,测试WiFi就ping一下网关。只有测试通过了,才敢说裁剪完成。