前言在 RTOS 中任务Task是有优先级之分的。操作系统的铁律是只要高优先级任务准备就绪它必须立刻抢占低优先级任务的 CPU 使用权。但在实际工程中多任务往往需要共享资源比如大家都想往同一个串口打印数据或者读写同一个 I2C 传感器。为了防止数据被打断写串了我们会引入“锁信号量 / Semaphore”。 灾难正是由这把“锁”引起的。一、 灾难的配方三个任务与一把锁假设你的系统里有三个任务任务 HHigh最高优先级负责极其关键的电机控制。任务 MMedium中等优先级负责跑跑流水灯或 UI 刷新。任务 LLow最低优先级负责读取 I2C 传感器。其中任务 H 和任务 L 共享了 I2C 总线总线由一把二值信号量Binary Semaphore保护。二、 幽灵现身优先级反转的全过程Bug 是在极其巧合的时序下爆发的L 拿到锁任务 L 开始运行拿到了 I2C 的锁正在读取数据。H 抢占并阻塞突然任务 H 被定时器唤醒了。H 抢占了 L想要读 I2C。但 H 发现锁在 L 手里于是 H 只能进入“阻塞态Blocked”无奈地等待 L 把数据读完释放锁。到这一步都很正常致命一击就在 L 准备继续运行并释放锁的时候任务 M 就绪了死锁爆发因为 M 的优先级高于 LM 无情地抢占了 L。现在M 霸占了 CPU 开心地跑着 UI 代码。而 L 迟迟得不到 CPU无法释放锁。结果最高优先级的 H原本只是等 L 一小会儿现在却被完全不相干的中优先级 M 间接给“卡死”了这就叫优先级反转。三、 终极救赎互斥量Mutex与优先级继承很多新手分不清二值信号量Binary Semaphore和互斥量Mutex的区别在 RTOS 里随便混用。 解决优先级反转的唯一正道就是使用 Mutex因为 Mutex 内置了一个伟大的机制优先级继承Priority Inheritance。它的魔法在于当高优先级 H 发现锁被低优先级 L 拿着时RTOS 会做一件极其聪慧的事——把 L 的优先级临时提升到和 H 一样高结局改变因为 L 临时变成了“最高优先级”中等优先级的 M 再也无法抢占它。L 会顺畅地跑完临界区代码释放锁然后把优先级降回去。紧接着H 顺利接手锁并运行完美避开死锁四、 总结在 RTOS 的世界里二值信号量用于“任务间的同步比如中断通知任务”而互斥量Mutex才应该用于“共享资源的互斥”。如果用错了锁你就是在给系统埋下一颗极难复现的定时炸弹。