第二章 Linux内核实时性基础:PREEMPT_RT补丁与抢占模型

好,咱们进入正题。这一章我打算聊聊Linux内核的实时性基础。说白了,就是怎么让Linux这个“通用操作系统”变得能干活、不拖沓。

你想想看,EtherCAT主站跑在Linux上,如果内核动不动就被别的进程抢走CPU,那实时性就崩了。我当年调试一个伺服驱动器,就因为内核抢占延迟,导致同步信号老是丢包。嗯,那会儿我才真正意识到——内核配置不对,硬件再好也白搭。

2.1 PREEMPT_RT补丁:实时性的灵魂

先说说PREEMPT_RT。这个补丁,说白了就是把Linux内核从一个“老好人”变成“铁面判官”。

标准Linux内核里,一个任务如果正在执行系统调用,它是不太愿意被抢断的。这就像你在写代码时,别人叫你吃饭,你说“等我写完这个函数”。但实时系统不行——高优先级任务来了,你必须立刻停下手里的活。

PREEMPT_RT补丁做了几件关键的事:

  • 中断线程化:把硬件中断处理程序变成可被抢占的内核线程。这样高优先级任务就能打断中断处理。
  • 自旋锁替换:把很多自旋锁换成互斥锁。自旋锁会让CPU空转,互斥锁会让出CPU给更重要的任务。
  • 优先级继承:防止优先级反转。这个我后面会细说。

核心观点:没有PREEMPT_RT,Linux的实时性上限大概在10毫秒左右。打了补丁后,可以做到几十微秒甚至更低。EtherCAT要求抖动在微秒级,所以这个补丁几乎是必选项。

我记得有一次,一个客户说他们的EtherCAT主站偶尔会丢帧。我一看内核配置,没打PREEMPT_RT。我说:“你先把这个补丁打上,别的先别动。”结果问题直接解决了。你说神奇不神奇?其实不神奇,就是内核抢占延迟从毫秒级降到了微秒级。

2.2 三种抢占模型:从温和到激进

Linux内核提供了三种抢占模型。我习惯把它们比作三种性格的人:

模型 内核配置选项 特点 适用场景
No-Force-Preempt CONFIG_PREEMPT_NONE 几乎不抢占,吞吐量优先 服务器、批处理系统
Voluntary-Preempt CONFIG_PREEMPT_VOLUNTARY 在自愿点可抢占,平衡型 桌面系统、轻度实时
Full-Preempt CONFIG_PREEMPT 几乎处处可抢占,延迟最低 实时系统、EtherCAT主站

2.2.1 No-Force-Preempt(非强制抢占)

这个模型,内核几乎不主动抢占。一个任务进了内核态,除非它自己主动让出CPU,否则别人别想插队。

好处是什么?吞吐量高。因为上下文切换少,CPU缓存命中率高。坏处呢?延迟不可控。一个低优先级的任务如果正在执行系统调用,高优先级任务就得干等着。

我见过有人拿这个模型跑EtherCAT,结果周期抖动能达到几十毫秒。嗯,这基本等于不能用。

2.2.2 Voluntary-Preempt(自愿抢占)

这个模型在代码里插入了很多“自愿抢占点”。说白了,就是内核在执行长路径时,会时不时停下来看看:“有没有更高优先级的任务要跑?”

它比No-Force-Preempt好一些,但还不够。为什么?因为“自愿”这个词本身就不可靠。如果内核代码路径里没有插入抢占点,那高优先级任务还是得等。

我的建议:如果你只是做原型验证,Voluntary-Preempt可以凑合用。但真要上产线,别犹豫,直接上Full-Preempt。

2.2.3 Full-Preempt(完全抢占)

这才是EtherCAT该用的模型。内核几乎在任何地方都可以被抢占——除了极少数临界区。

它的核心思想是:延迟确定性比吞吐量更重要。你想想看,EtherCAT的同步周期是固定的,如果某个周期因为内核抢占而延迟了,整个网络都可能出问题。

我个人的习惯是:只要跑EtherCAT,内核配置里直接选Full-Preempt,然后再加上PREEMPT_RT补丁。这两个配合起来,效果最好。

2.3 配置实战:怎么选?怎么配?

好,理论说完了,咱们来点实际的。怎么配置内核?

首先,你得确保内核源码已经打了PREEMPT_RT补丁。这个补丁可以从 kernel.orgpub/linux/kernel/projects/rt/ 目录下载。版本要跟你的内核版本匹配。

然后,在 make menuconfig 里,找到以下选项:

General setup --->
  Preemption Model --->
    (X) Fully Preemptible Kernel (RT)

选上这个,内核就会启用Full-Preempt模式,并且带上PREEMPT_RT的特性。

另外,我建议你检查一下这几个配置:

  • CONFIG_HZ_1000:把时钟频率设为1000Hz,提高定时器精度。
  • CONFIG_NO_HZ_FULL:把CPU核心从时钟中断中解放出来,减少抖动。
  • CONFIG_RCU_NOCB_CPU:把RCU回调迁移到专门的CPU核心上。

注意:Full-Preempt模式会带来大约5%-10%的吞吐量损失。但做实时系统,这个代价是值得的。我曾经为了省这点性能,选了Voluntary-Preempt,结果现场出了问题,返工成本远高于那点性能损失。

2.4 避坑指南:我踩过的几个坑

最后,分享几个我实际遇到的坑:

  • 坑一:忘了打补丁。有人以为选了Full-Preempt就等于有了实时性。不对!没有PREEMPT_RT补丁,Full-Preempt的效果会大打折扣。
  • 坑二:中断亲和性没配。EtherCAT网卡的中断应该绑定到专门的核心上,别让其他任务干扰它。
  • 坑三:内核版本太新。PREEMPT_RT补丁通常滞后于主线内核。我建议用LTS版本,比如5.10或5.15,这些版本的RT补丁比较成熟。

我曾经在一个项目里,用了最新的6.0内核,结果PREEMPT_RT补丁还没完全稳定,导致系统偶尔会死锁。后来老老实实换回5.10 LTS,问题就没了。嗯,有时候“新”不等于“好”。

好了,这一章就到这里。下一章咱们聊聊EtherCAT主站的内核配置细节,包括网卡驱动、中断处理、CPU隔离这些实战内容。到时候我会带一些具体的配置示例,保证你能直接上手用。