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提供了几种机制:
- 优先级继承协议:当高优先级任务被低优先级任务持有的锁阻塞时,低优先级任务临时继承高优先级任务的优先级。这样中间优先级的任务就无法抢占它了。
- 关中断 + 自旋锁:对于非常短的临界区,直接关中断加自旋。虽然粗暴,但有效。
- 任务绑定:把可能产生优先级反转的任务绑定到同一个核上。这样问题就退化成了单核问题,好处理得多。
我个人比较推荐第三种方式。说白了,SMP的实时性问题,很多时候可以通过合理的任务分配来规避。不是所有任务都适合跑在多核上。
20.4 实践中的避坑指南
讲了这么多理论,来点实际的。我在做RT-Thread SMP移植时,总结了几条经验:
- 关中断不能关其他核:单核上习惯用关中断来保护临界区。多核上,关中断只关当前核,其他核照样能访问共享数据。必须用自旋锁。
- 自旋锁的持有时间要极短:自旋锁是忙等的,持有时间长了,其他核就在那空转。我一般要求自旋锁内的代码不超过100条指令。
- 中断处理要轻量:中断上下文中尽量别获取自旋锁。如果必须获取,要确保不会导致死锁。
- 使用核间通信代替共享数据:能发消息就别共享变量。消息传递天然是线程安全的。
总结一下:SMP对实时性的影响,核心在于“不确定性”。WCET分析要考虑到核间干扰,优先级反转在SMP下会跨核传播。解决思路是:短临界区、合理分配任务、用好优先级继承。记住,实时系统追求的不是“快”,而是“可预测”。
下一章,我们会讲SMP系统的调试与性能分析。到时候我会分享一些实际调试中遇到的“坑”,以及怎么用RT-Thread的工具来定位问题。嗯,那才是真正有意思的部分。