
1997 年 7 月,美国航空航天局 NASA 的探路者号(Pathfinder)登陆火星,准备执行 NASA 发现计划的首个任务,旨在研究火星的大气和其他物质。尽管现今从科研角度来看这个项目是成功的,但其成功之路在刚开始的时候却并不平坦。
在登陆不久后,科研工作者就发现了一件很不对劲的事 —— 探路者在火星表面的数据采集十分缓慢,并且出现了显著的延迟!经过检查,发现其不断在重启自己,再次经过一系列深入排查,最终定位到了问题的元凶 —— 优先级反转!由于优先级反转导致关键的任务没有及时得到运行,并且这一延迟被一个看门狗识别到,最终执行了重置操作。
如果你在嵌入式开发中用过 RTOS,那相信你对“优先级反转”这个名词并不陌生,至少听说过。是的,探路者也用了 RTOS,其使用的是 VxWorks。找到问题后,NASA 也是快速修复了这个 BUG,但究其原因,值得我们深入讨论。
于 RTOS 中,调度算法在决定何时将哪个任务分配给处理器执行时扮演着至关重要的角色。尤其工业领域,抢占式固定优先级调度是执行周期性任务的首选方法。每个任务被静态地分配一个固定优先级,调度器总是通过抢占较低优先级任务的执行来调度高优先级的就绪任务。换句话说,较高优先级的任务可以抢占低优先级任务的运行,而较低优先级的任务不能抢占较高优先级任务的运行。低优先级任务只有在不存在高优先级任务就绪的情况下恢复执行。
对于调度一组周期性实时任务而言,早期成熟的固定优先级调度算法的稳定性建立在一个前提之上,即这些任务不会共享除 CPU 以外的其他资源。然而这一前提并不总是成立的,一旦任务开始共享资源,那么问题就会出现。优先级反转问题就是在 RTOS 中由于共享资源而产生的同步问题。
我们知道现代的处理任务通常会共享各种资源,如数据结构,存储器,文件,寄存器,IO单元,通信总线等。当多个任务共享一个资源时,为了避免出现资源竞争导致的数据异常,通常会在使用资源时对资源进行加锁,使用完后解锁。当一个资源被加了锁,此时有另一个任务需要获取,那其会获取失败或进行阻塞等待。
此时一旦产生了阻塞,就等于埋下了定时炸弹!
我们现在假定有两个任务 T1 和 T2 正在运行,其中优先级情况为 T2>T1,并且两者共享一个资源 S1,它们之间的一种运行状态如下图所示:

首先 T1 正常运行,期间对 S1 进行加锁并对该资源进行处理。到了 t1 时刻,T1 对于 S1 的处理还并未完成,而更高优先级的 T2 也想操作 S1,并且尝试获取 S1,但由于 S1 已经被 T1 加锁,因此 T2 获取失败,于是在 t2 时刻进入阻塞状态,将运行权交还给 T1 。到 t3 时刻 T1 对 S1 的操作结束,解锁该资源。此时由于资源被解锁,T2 得以抢占 T1 运行并获取资源进行处理。到 t4 时刻 T2 的操作也完成从而释放运行权,T1 再次运行。
以上是由一个共享资源导致的高优先级任务必须阻塞等待低优先级任务运行的情况,这种属于不可避免的阻塞,可以认为是一种不太理想但还算正常的情况(如果要避免这种情况需要从根本上禁止高低优先级对资源的共享,或者通过手动编码保证只存在低优先级等待高优先级处理资源的情况)。而一旦再引入一个中优先级任务,那么事情可能变得无法控制:

现在我们将任务扩充到 T3,并重新分配下优先级使得 T3>T2>T1。同样,t1 时刻低优先级的T1持有S1并发生最高优先级的T3进行抢占,但由于S1被加锁导致T3获取资源失败进入阻塞,到这里的运行逻辑与上例保持一致,然而,到t3时刻,中优先级的T2抢占了低优先级T1的运行,这里,整个运行逻辑进入了不可控的地步:T2实际上并不参与共享资源S1的任何操作,S1还是被T1持有,但T1的运行被抢占了,最高优先级的T3却还在等待T1解锁资源从而获得运行权。
这种情况就被称为优先级反转,其本质是高优先级任务因等待低优先级任务释放资源而被阻塞,同时又有其他中优先级任务抢占了 CPU,使得低优先级任务得不到执行机会,进而导致高优先级任务长时间无法继续运行。
优先级反转的发生严重破坏了实时系统中任务调度的确定性,可能导致关键任务错过截止时间,从而引发系统故障。
为了解决优先级反转问题,操作系统通常引入了一些机制,例如优先级继承(Priority Inheritance)和优先级天花板(Priority Ceiling)。
在优先级继承机制中,如果一个高优先级任务因资源被低优先级任务占用而被阻塞,那么低优先级任务的优先级会被临时提升至高优先级任务的优先级,以尽快释放资源。这可以有效减少高优先级任务的等待时间,避免中优先级任务抢占低优先级任务的执行:

由上图可以看到,t1时刻T3在尝试获取资源的时候使得T1继承了其优先级,于是在t2时刻T1将以T3的高优先级开始运行,由于T1优先级临时得到抬升,自然此时比T3优先级低的中优先级任务T2并不会在T1运行过程中来抢占 CPU 资源。如果没有更高优先级的任务抢占,那么T1将保持运行到释放资源,紧接着让T3抢占并获取资源,同时恢复原有的优先级。T3运行完后中优先级的T2也能正常抢占。这种机制保证了T3能够尽快获取到资源,也避免了高优先级等待低优先级释放资源而低优先级却没在运行的不可控场景。
可见优先级继承机制极大地减轻了优先级反转带来的影响和危害,NASA 当时也正是启用了 VxWorks 中的优先级继承功能从而解决了问题。然而,优先级继承机制虽然有效,但也有其局限性。例如,它无法完全避免优先级反转的发生,只是在发生时减轻其影响(发生反转后对当前运行任务的优先级进行提升)。此外,优先级继承可能会导致任务优先级的频繁变化,从而增加系统的调度开销。
另一种更为严格的解决方案是优先级天花板机制。该机制为每个资源分配一个优先级天花板,即所有可能访问该资源的任务中最高的优先级,该值一般是创建资源锁时预设的一个静态值。当一个任务获取该资源时,它的优先级会被提升至该天花板优先级,从而避免任何其他任务抢占它。这样可以确保任务在持有资源期间不会被中断,从根本上防止优先级反转的发生:

由上图可见,T1首先以低优先级开始运行,在t1时刻,尝试获取S1,于此同时触发优先级天花板机制,其优先级直接提升到预设的天花板优先级,在这里就是T3所对应的高优先级,这样,在没有更高优先级任务的情况下 T1 可以保持运行到释放资源,在这过程中即使是原本最高优先级的T3也无法抢占,相比于优先级继承机制下T3抢占后动态提升T1的优先级再让出 CPU 给T1 来执行,这种机制显然更彻底,拥有更小的调度开销且可以使T3响应资源处理的时间更短!
在实际应用中,优先级天花板机制通常比优先级继承更为保守,但也更为安全。它广泛应用于对实时性要求极高的系统中,如航空航天、工业控制等领域。
综上,优先级反转是实时系统中一个不可忽视的问题,它可能对系统的稳定性和实时性造成严重影响。通过合理使用优先级继承或优先级天花板机制,可以有效规避这一问题,保障系统任务的正常调度与执行。
最后,如果你还想更深入地理解,可以关注公众号 WKJay,后续文章我会结合 RT-Thread 来调试并逐步讲解其中的调度和运行逻辑。

1885

被折叠的 条评论
为什么被折叠?



