20、实时性分析:SMP对实时性的影响、最坏情况执行时间、优先级反转

好,咱们进入第20章。说实话,这一章是很多做实时系统工程师最头疼的部分。SMP听起来很美——多核嘛,性能翻倍。但实时性呢?搞不好就是个灾难。

我见过不少项目,单核上跑得好好的RT-Thread,一上多核,任务就开始“抽风”。该响应的没响应,该切换的没切换。为什么?因为SMP对实时性的影响,远比想象中复杂。

20.1 SMP对实时性的“双刃剑”效应

先说说好的方面。SMP能提升吞吐量,这是事实。比如一个计算密集型的任务,单核要跑100ms,双核分摊一下,可能60ms就搞定了。但实时性看的是“确定性”,不是“平均速度”。

你想想看,单核系统里,中断来了,CPU立马响应。多核呢?中断可能被分配到任意一个核上。如果那个核正在跑一个关中断的临界区,中断就得等着。这就是第一个坑:中断响应延迟的不确定性

核心观点:SMP提升了平均性能,但可能恶化最坏情况响应时间。实时系统要的是“最坏情况有界”,而不是“平均情况优秀”。

我在项目中遇到过这样一个场景:一个电机控制任务,周期是1ms。单核上跑得好好的,抖动在50μs以内。移植到双核SMP后,抖动直接飙到300μs。查了半天,发现是另一个核上的DMA中断抢占了总线,导致这个核的定时器中断被延迟了。

所以,SMP对实时性的影响,说白了就是引入了核间干扰。包括:

  • 总线竞争:多个核同时访问内存或外设,谁先谁后?
  • 缓存一致性开销:一个核改了数据,另一个核的缓存得失效重载。
  • 核间中断延迟:一个核通知另一个核,IPI(核间中断)本身就有开销。
  • 锁竞争:多个核抢同一把锁,优先级高的任务可能被优先级低的锁住。

20.2 最坏情况执行时间(WCET)分析

WCET,全称Worst-Case Execution Time。这是实时系统的命根子。单核上,WCET分析已经够难了。多核上?难上加难。

单核的WCET主要看指令流、分支预测、缓存命中率。多核还得考虑核间干扰。同一个函数,在单核上WCET是100μs,在多核上可能变成500μs。为什么?因为另一个核可能正在疯狂刷缓存,导致你的代码每次都要从内存重新加载。

我个人习惯把多核WCET分解成三部分:

组成部分 说明 典型影响因素
本地执行时间 任务本身在核上运行的时间 指令数、分支、缓存命中
资源竞争延迟 等待共享资源(总线、内存、锁)的时间 其他核的访问频率、锁持有时间
调度延迟 任务被调度到核上执行的等待时间 核的负载、优先级、亲和性设置

嗯,这里要注意:资源竞争延迟是最难估算的。因为它取决于其他核的行为,而其他核的行为又取决于它们的任务集。这就成了一个“互相依赖”的复杂问题。

我的建议:在做WCET分析时,不要只测“典型情况”。一定要构造最坏场景——让所有核同时访问共享资源。我曾经用这种方法,发现一个任务的WCET比预期大了4倍。还好提前发现了,不然产品上市后肯定出问题。

对于RT-Thread SMP,我建议你重点关注以下几个地方的WCET:

  • 临界区:持有自旋锁的时间。这个时间必须短,否则其他核会死等。
  • 中断服务程序:中断处理中访问共享数据的时间。
  • 任务切换:调度器切换任务时,需要操作全局就绪队列,这个操作是串行化的。
  • 系统调用:比如释放信号量、发送消息,这些都可能触发核间通信。

20.3 优先级反转——SMP下的“升级版”

优先级反转,老生常谈了。单核上,一个低优先级任务持有锁,高优先级任务等锁,中间优先级任务抢跑,导致高优先级任务被无限期阻塞。解决方案是优先级继承或优先级天花板协议。

但在SMP下,情况更复杂。为什么?因为锁的持有者和等待者可能在不同的核上

举个例子:

  • 核0上跑着低优先级任务L,它持有一把锁。
  • 核1上跑着高优先级任务H,它想获取同一把锁,被阻塞。
  • 核0上还有一个中等优先级任务M,它抢占了L。

你看,任务H在核1上干等,而核0上的任务M正在愉快地运行。任务L被M抢占了,锁释放不了。这就是SMP下的跨核优先级反转

警告:在SMP系统中,优先级反转的影响范围更大。单核上,反转只影响当前核上的任务。多核上,一个核上的低优先级任务,可能阻塞另一个核上的高优先级任务。我曾经调试过一个案例,一个核上的打印任务(低优先级)持有了锁,导致另一个核上的控制任务(高优先级)被阻塞了整整2ms。这在单核上几乎不可能发生。

怎么解决?RT-Thread SMP提供了几种机制:

  1. 优先级继承协议:当高优先级任务被低优先级任务持有的锁阻塞时,低优先级任务临时继承高优先级任务的优先级。这样中间优先级的任务就无法抢占它了。
  2. 关中断 + 自旋锁:对于非常短的临界区,直接关中断加自旋。虽然粗暴,但有效。
  3. 任务绑定:把可能产生优先级反转的任务绑定到同一个核上。这样问题就退化成了单核问题,好处理得多。

我个人比较推荐第三种方式。说白了,SMP的实时性问题,很多时候可以通过合理的任务分配来规避。不是所有任务都适合跑在多核上。

20.4 实践中的避坑指南

讲了这么多理论,来点实际的。我在做RT-Thread SMP移植时,总结了几条经验:

  • 关中断不能关其他核:单核上习惯用关中断来保护临界区。多核上,关中断只关当前核,其他核照样能访问共享数据。必须用自旋锁。
  • 自旋锁的持有时间要极短:自旋锁是忙等的,持有时间长了,其他核就在那空转。我一般要求自旋锁内的代码不超过100条指令。
  • 中断处理要轻量:中断上下文中尽量别获取自旋锁。如果必须获取,要确保不会导致死锁。
  • 使用核间通信代替共享数据:能发消息就别共享变量。消息传递天然是线程安全的。

总结一下:SMP对实时性的影响,核心在于“不确定性”。WCET分析要考虑到核间干扰,优先级反转在SMP下会跨核传播。解决思路是:短临界区、合理分配任务、用好优先级继承。记住,实时系统追求的不是“快”,而是“可预测”。

下一章,我们会讲SMP系统的调试与性能分析。到时候我会分享一些实际调试中遇到的“坑”,以及怎么用RT-Thread的工具来定位问题。嗯,那才是真正有意思的部分。