论
论坛助手
优先级反转是实时系统里最经典的坑之一:一个高优先级任务 H 需要一把互斥量,而这把锁正被低优先级任务 L 拿着。L 还没跑完临界区,中优先级任务 M 就绪了——调度器按优先级让 M 抢占 L。结果 H 在等 L,L 在等 M 跑完,M 却可能一直有活干。H 的阻塞时间变得不可预测,这就是“反转”:M 实际上在 H 之前执行了。
这不是教科书里的假想敌。1997 年火星探路者号(Mars Pathfinder)着陆火星后频繁自动重启,JPL 事后定位就是 VxWorks 上的优先级反转:气象数据采集任务(高优先级)等待被低优先级任务持有的互斥量,而中优先级的通信任务不断抢占,导致看门狗超时复位。最后的修复是在地面上传补丁,打开互斥量的优先级继承选项——一个真实到不能再真实的案例。
优先级继承协议的思路很直白:当 H 阻塞在 L 持有的锁上时,L 临时“继承” H 的优先级,于是 M 抢占不了 L,L 赶紧跑完临界区释放锁,优先级再掉回去。继承是可传递的(链式等待时逐级上传),它把 H 的阻塞时间限定为“至多一个临界区的长度”,从不可预测变为可分析。代价是实现复杂,且多把锁时仍可能死锁。
优先级天花板协议走得更远:给每把互斥量设定一个“天花板”——等于所有可能使用这把锁的任务中的最高优先级。任务一旦加锁成功,立刻被提升到天花板优先级,解锁后恢复。这样 H 阻塞至多一次(L 持有锁的那一个临界区),而且天然避免死锁。代价是可能发生“不必要的提升”:L 拿锁期间优先级虚高,会延迟一些本可运行的中优先级任务。
在 VxWorks 里落地很简单:创建互斥信号量时用 semMCreate(SEM_Q_PRIORITY | SEM_INVERSION_SAFE),SEM_INVERSION_SAFE 就是打开优先级继承的开关。注意它只对互斥信号量有效,计数信号量没有这个语义。VxWorks 没有给天花板协议提供一键开关,需要的话可以用 taskPrioritySet() 手动把临界区包起来,或者在 RTP 里用 POSIX 互斥量的优先级保护属性。实战建议:所有保护共享资源的互斥量默认打开 SEM_INVERSION_SAFE;临界区尽量短,这是比任何协议都管用的根本解;做最坏情况分析时,把“继承导致的阻塞”计入 H 的响应时间预算,而不是假装它不存在。
这不是教科书里的假想敌。1997 年火星探路者号(Mars Pathfinder)着陆火星后频繁自动重启,JPL 事后定位就是 VxWorks 上的优先级反转:气象数据采集任务(高优先级)等待被低优先级任务持有的互斥量,而中优先级的通信任务不断抢占,导致看门狗超时复位。最后的修复是在地面上传补丁,打开互斥量的优先级继承选项——一个真实到不能再真实的案例。
优先级继承协议的思路很直白:当 H 阻塞在 L 持有的锁上时,L 临时“继承” H 的优先级,于是 M 抢占不了 L,L 赶紧跑完临界区释放锁,优先级再掉回去。继承是可传递的(链式等待时逐级上传),它把 H 的阻塞时间限定为“至多一个临界区的长度”,从不可预测变为可分析。代价是实现复杂,且多把锁时仍可能死锁。
优先级天花板协议走得更远:给每把互斥量设定一个“天花板”——等于所有可能使用这把锁的任务中的最高优先级。任务一旦加锁成功,立刻被提升到天花板优先级,解锁后恢复。这样 H 阻塞至多一次(L 持有锁的那一个临界区),而且天然避免死锁。代价是可能发生“不必要的提升”:L 拿锁期间优先级虚高,会延迟一些本可运行的中优先级任务。
在 VxWorks 里落地很简单:创建互斥信号量时用 semMCreate(SEM_Q_PRIORITY | SEM_INVERSION_SAFE),SEM_INVERSION_SAFE 就是打开优先级继承的开关。注意它只对互斥信号量有效,计数信号量没有这个语义。VxWorks 没有给天花板协议提供一键开关,需要的话可以用 taskPrioritySet() 手动把临界区包起来,或者在 RTP 里用 POSIX 互斥量的优先级保护属性。实战建议:所有保护共享资源的互斥量默认打开 SEM_INVERSION_SAFE;临界区尽量短,这是比任何协议都管用的根本解;做最坏情况分析时,把“继承导致的阻塞”计入 H 的响应时间预算,而不是假装它不存在。

