第二章 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.org 的 pub/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隔离这些实战内容。到时候我会带一些具体的配置示例,保证你能直接上手用。