4、Linux内核移植与裁剪:从源码到工业级实时系统
好,咱们进入第四章。这一章的内容,说白了就是BSP开发里最核心的体力活——把Linux内核请到你的板子上,再把它瘦身成适合工业现场的样子。我做了这么多年BSP,每次拿到新板子,第一件事就是跟内核源码打交道。这里面坑不少,但摸熟了也就那么回事。
4.1 内核源码获取与分支选择
先说说源码从哪来。我个人习惯,永远只从kernel.org或者芯片厂商的官方仓库拉代码。别图方便去GitHub上随便找个fork,你根本不知道里面改了什么。
内核的分支策略,我建议你记住这张表:
| 分支类型 | 标签示例 | 适用场景 | 风险等级 |
|---|---|---|---|
| 主线(Mainline) | v6.6 | 新功能验证、原型开发 | 中 |
| 稳定(Stable) | v6.6.10 | 工业产品量产 | 低 |
| 长期支持(LTS) | v6.1.y | 五年生命周期产品 | 极低 |
| 芯片厂商分支 | ti-linux-6.1.y | 特定SoC适配 | 视厂商维护质量 |
获取源码的命令很简单:
# 克隆LTS内核
git clone --depth 1 --branch v6.1.75 \
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
# 或者只拉指定版本
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.75.tar.xz
tar -xf linux-6.1.75.tar.xz
嗯,这里要注意——--depth 1只拉最新提交,能省下大量时间。但如果你要做git bisect查问题,就得全量克隆了。
4.2 内核配置系统:Kconfig与Makefile
内核的配置系统,说白了就是Kconfig和Makefile这两兄弟。Kconfig定义“有什么可以选”,Makefile决定“选了之后怎么编译”。
咱们先看Kconfig。它长这样:
config MY_DRIVER
tristate "My Custom Driver Support"
depends on SPI
select REGMAP
help
Enable support for my custom SPI device.
Say Y here if you have this hardware.
这里几个关键字我解释一下:
- tristate:三态——Y(编入内核)、M(模块)、N(不编译)。工业产品我建议能用Y就用Y,减少模块加载的麻烦。
- depends on:依赖关系。比如这个驱动依赖SPI子系统,没选SPI它就不可见。
- select:反向选择。选了它,REGMAP会被自动选上。这招用的时候要小心,搞不好会循环依赖。
Makefile那边就简单多了:
obj-$(CONFIG_MY_DRIVER) += my_driver.o
如果CONFIG_MY_DRIVER是Y,my_driver.o就编进内核;是M,就编成模块;是N,就不编译。
避坑指南:我曾经在一个项目里,把驱动配成了M,但文件系统里忘了放模块。结果板子启动后驱动没加载,设备不工作。查了两天才找到原因。所以工业产品里,能用Y就别用M。
4.3 最小系统裁剪策略
工业产品的存储空间通常很金贵。Flash可能只有16MB、32MB,你得把内核裁到能塞进去。我一般按这个步骤来:
- 先跑一次默认配置——
make defconfig,看看基线有多大。 - 关掉所有不用的架构——你跑ARM,就把x86、MIPS、RISC-V全关了。
- 关掉调试选项——
CONFIG_DEBUG_KERNEL、CONFIG_PRINTK(如果不需要日志)、CONFIG_MAGIC_SYSRQ。 - 精简驱动——只留你板子上有的外设驱动。网卡、串口、I2C、SPI、GPIO,其他的全干掉。
- 关掉文件系统支持——你只用一种文件系统,就别把ext2/3/4、btrfs、xfs全编进去。
- 关掉网络协议栈——如果设备不联网,把IPv6、Netfilter、Wireless全关了。
我举个例子。之前做一款工业网关,Flash只有16MB。默认配置编出来内核8.5MB,根本塞不下。经过一轮裁剪:
| 裁剪项 | 节省空间 |
|---|---|
| 去掉调试符号 | ~2.1 MB |
| 精简驱动(只留必要外设) | ~3.4 MB |
| 关掉IPv6和Netfilter | ~0.8 MB |
| 去掉不用的文件系统 | ~0.5 MB |
| 最终内核大小 | ~1.7 MB |
你看,从8.5MB裁到1.7MB,完全可行。
4.4 工业实时性内核配置
工业现场对实时性要求高。PLC、运动控制、数据采集,这些场景下,标准内核的调度延迟可能达到几十毫秒,完全不可接受。怎么办?上PREEMPT_RT补丁。
配置实时内核,我一般这么操作:
- 打PREEMPT_RT补丁——从kernel.org下载对应版本的补丁,
patch -p1 < patch-6.1.75-rt32.patch。 - 配置内核——
make menuconfig,进入General Setup -> Preemption Model。 - 选择实时模式——选
Fully Preemptible Kernel (Real-Time)。
关键配置项我列一下:
| 配置项 | 推荐值 | 说明 |
|---|---|---|
| CONFIG_PREEMPT_RT | y | 开启完全抢占,实时调度的基础 |
| CONFIG_HZ_1000 | y | 时钟频率1000Hz,调度粒度更细 |
| CONFIG_TIMERFD | y | 高精度定时器接口,用户态实时必备 |
| CONFIG_IRQ_FORCED_THREADING | y | 强制中断线程化,减少中断延迟抖动 |
| CONFIG_RCU_NOCB_CPU | y | RCU回调卸载,避免在实时任务中执行 |
配置完实时内核,别忘了测试。我习惯用cyclictest跑一下:
# 安装rt-tests
apt-get install rt-tests
# 运行测试,测量最大延迟
cyclictest -t 5 -p 80 -i 1000 -l 100000
# 输出示例
# T: 0 ( 1234) P: 80 I:1000 C: 100000 Min: 2 Act: 5 Avg: 4 Max: 17
看到Max值了吗?17微秒。这就是实时内核的威力。
总结一下:内核移植与裁剪,说白了就是三件事——选对源码、配好功能、裁掉冗余。实时性配置是工业产品的加分项,但不是所有场景都需要。如果你的设备只是做数据采集,标准内核加高优先级线程就够了。但如果是运动控制、PLC这类硬实时场景,PREEMPT_RT是必须的。