第四章 Linux内核裁剪与配置:从源码到定制镜像
内核裁剪,说白了就是给你的硬件平台量身定制一套操作系统内核。我做了这么多年嵌入式,见过太多人拿着全功能的内核直接往板子上烧——结果512MB的Flash塞不下,或者启动慢得像蜗牛。这一章,我们就来彻底搞懂内核裁剪的每一个环节。
4.1 内核源码获取与版本选择
选内核版本,其实是个技术活。我个人习惯遵循一个原则:不追新,不守旧,选LTS。
| 版本类型 | 特点 | 适用场景 |
|---|---|---|
| LTS(长期支持) | 维护周期5-6年,稳定 | 产品量产、工业控制 |
| Stable(稳定版) | 每2-3月发布,修复及时 | 开发验证、原型机 |
| Mainline(主线) | 最新特性,风险高 | 芯片BSP开发、尝鲜 |
获取源码的方式很简单,我推荐直接用git克隆:
# 获取LTS 5.10版本
git clone --depth 1 --branch v5.10 https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
# 或者下载完整历史(不推荐,太占空间)
git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
--depth 1 只拉取最新提交,能省下至少1GB的.git目录。我在一个项目中因为没加这个参数,CI流水线每次拉代码都要等15分钟——后来改了,30秒搞定。
4.2 Kconfig与Makefile机制
内核的配置系统,核心就是Kconfig和Makefile这对搭档。你想想看,几千个驱动、几百个功能,怎么管理?靠的就是这套机制。
4.2.1 Kconfig:配置菜单的骨架
Kconfig定义了每个配置项的属性:类型、依赖、默认值、帮助信息。看一个典型的例子:
config CONFIG_DRM_IMX
tristate "DRM Support for i.MX"
depends on DRM && (ARCH_MXC || COMPILE_TEST)
select DRM_KMS_HELPER
select DRM_GEM_CMA_HELPER
help
Choose this option if you have an i.MX SoC.
This driver provides support for the Display Controller.
这里有几个关键点:
- tristate:三态选择——Y(编译进内核)、M(编译为模块)、N(不编译)
- depends on:依赖关系,不满足就不显示
- select:反向依赖,选中这个会自动选上别的
DRM_KMS_HELPER 手动关了,结果 DRM_IMX 编译报错。后来才明白 select 是强制依赖,不能手动取消。所以,不要手动修改Kconfig文件来强制关闭某个选项,除非你清楚所有连锁反应。
4.2.2 Makefile:编译的指挥棒
Makefile决定了哪些文件参与编译,以及怎么编译。内核的Makefile写得非常巧妙:
# 根据配置变量决定编译目标
obj-$(CONFIG_DRM_IMX) += imx-drm.o
imx-drm-objs := imx-drm-core.o imx-drm-crtc.o imx-drm-plane.o
# 如果是模块,加上模块信息
ifdef CONFIG_DRM_IMX_MODULE
imx-drm-objs += imx-drm-module.o
endif
你看,obj-$(CONFIG_DRM_IMX) 这个写法很妙——如果配置是Y,就编译进内核;如果是M,就编译成模块;如果是N,这行就变成空,什么都不编译。
4.3 内核配置裁剪策略
裁剪不是乱删,得有策略。我总结了一套四步裁剪法:
- 硬件驱动裁剪:只保留板子上实际用到的外设驱动
- 文件系统裁剪:去掉不需要的文件系统支持(比如你的板子用ext4,就别选btrfs、xfs)
- 网络协议栈裁剪:如果不需要IPv6、IPsec、Netfilter,统统关掉
- 调试与追踪裁剪:产品发布前,关掉所有debug选项、trace、printk
实际操作中,我习惯这样开始:
# 基于当前架构生成默认配置
make ARCH=arm defconfig
# 然后基于这个配置进行裁剪
make ARCH=arm menuconfig
核心原则:先做减法,再做加法。从默认配置开始,关掉所有不需要的,再打开必须的。这样不容易漏掉关键驱动。
下面这张图展示了内核配置的完整流程:
4.4 内核编译与模块管理
配置好了,就该编译了。这里我分享几个实战技巧。
4.4.1 编译内核与设备树
# 编译内核镜像
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage -j4
# 编译设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
# 编译模块
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
# 安装模块到目标目录
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- \
INSTALL_MOD_PATH=/path/to/rootfs modules_install
我的习惯:用 -j4 或 -j$(nproc) 并行编译,能快不少。但注意,如果虚拟机内存只有2GB,别开太多并行,否则会OOM。我曾经在1GB的云服务器上开 -j8,结果编译到一半系统直接挂了。
4.4.2 模块的加载与卸载
模块管理是嵌入式开发的基本功。常用的命令就这几个:
| 命令 | 作用 | 示例 |
|---|---|---|
insmod |
加载模块(不处理依赖) | insmod my_driver.ko |
modprobe |
加载模块(自动处理依赖) | modprobe my_driver |
rmmod |
卸载模块 | rmmod my_driver |
lsmod |
列出已加载模块 | lsmod | grep my |
modinfo |
查看模块信息 | modinfo my_driver.ko |
rmmod 了一个正在被使用的USB驱动,结果系统瞬间卡死。后来学乖了,卸载前先用 lsmod | grep 看看有没有进程在用,或者用 modprobe -r 安全卸载。
4.4.3 编译优化选项
对于ARM架构,编译优化能显著影响性能和镜像大小。我常用的优化选项:
# 在Makefile或环境变量中设置
export CFLAGS="-Os -march=armv7-a -mtune=cortex-a7 -pipe"
# 或者在menuconfig中设置
# General setup → Compiler optimization level → Optimize for size (-Os)
几个优化级别的选择建议:
- -Os:优化尺寸,适合Flash紧张的嵌入式设备
- -O2:平衡性能与尺寸,适合大多数场景
- -O3:极致性能,但镜像大,适合高性能平台
实战数据:我在一个Cortex-A7的项目上做过对比,用 -Os 比 -O2 内核镜像小了约18%,启动速度快了5%,但CPU密集型任务性能下降了约8%。所以,没有银弹,根据你的硬件资源做取舍。
4.5 配置裁剪的避坑指南
最后,分享几个我亲身经历的教训:
- 别乱关
CONFIG_OF:设备树支持,ARM平台必须开,关了内核起不来 - 注意
CONFIG_CMDLINE:如果bootloader没传内核参数,这里要写默认启动参数 - 模块依赖要理清:用
modprobe --show-depends查看依赖链 - 保存配置快照:每次裁剪前,备份
.config,方便回退