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内核。之前有个项目用了主线内核,三个月后API变了,驱动全得重写。从那以后,我坚持用LTS,省心。

获取源码的命令很简单:

# 克隆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,你得把内核裁到能塞进去。我一般按这个步骤来:

  1. 先跑一次默认配置——make defconfig,看看基线有多大。
  2. 关掉所有不用的架构——你跑ARM,就把x86、MIPS、RISC-V全关了。
  3. 关掉调试选项——CONFIG_DEBUG_KERNEL、CONFIG_PRINTK(如果不需要日志)、CONFIG_MAGIC_SYSRQ。
  4. 精简驱动——只留你板子上有的外设驱动。网卡、串口、I2C、SPI、GPIO,其他的全干掉。
  5. 关掉文件系统支持——你只用一种文件系统,就别把ext2/3/4、btrfs、xfs全编进去。
  6. 关掉网络协议栈——如果设备不联网,把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,完全可行。

注意:裁剪别太狠。我之前裁掉了一个看起来没用的GPIO驱动,结果板子上的LED不亮了,调试花了一整天。每个驱动关掉之前,确认一下硬件上真的没有用到。

4.4 工业实时性内核配置

工业现场对实时性要求高。PLC、运动控制、数据采集,这些场景下,标准内核的调度延迟可能达到几十毫秒,完全不可接受。怎么办?上PREEMPT_RT补丁。

配置实时内核,我一般这么操作:

  1. 打PREEMPT_RT补丁——从kernel.org下载对应版本的补丁,patch -p1 < patch-6.1.75-rt32.patch。
  2. 配置内核——make menuconfig,进入General Setup -> Preemption Model。
  3. 选择实时模式——选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回调卸载,避免在实时任务中执行
我的实测数据:在ARM Cortex-A7平台上,标准内核的中断响应延迟约50-200μs,开启PREEMPT_RT后降到5-15μs。对于工业控制来说,这个量级完全够用。

配置完实时内核,别忘了测试。我习惯用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是必须的。

Linux内核移植与裁剪知识体系 内核移植与裁剪 源码获取与分支 Kconfig/Makefile 最小系统裁剪 实时性配置 主线/稳定/LTS 芯片厂商分支 tristate/depends/select obj-$(CONFIG_xxx) 关调试/精简驱动 去网络/去多余FS PREEMPT_RT补丁 HZ=1000/中断线程化 核心原则 1. 源码选LTS,稳定压倒一切 2. 配置用Y不用M,减少模块依赖 3. 裁剪要适度,留有余地 4. 实时性按需配置,不要盲目上RT