优先级反转问题
文章目录
- 优先级反转的发生过程
- **阶段 1:初始状态 & 低优先级任务获取锁**
- **阶段 2:高优先级任务就绪并请求锁**
- **阶段 3:高优先级任务请求同一把锁 - 阻塞!**
- **阶段 4:调度器切换回低优先级任务 - 但被中优先级任务插队!**
- **阶段 5:中优先级任务长时间运行**
- **阶段 6:中优先级任务完成 & 低优先级任务终于运行并释放锁**
- **核心问题总结:优先级反转的关键时刻在哪里?**
- 优先级继承如何解决问题
- 原始问题场景回顾(未启用优先级继承):
- 启用优先级继承后的解决流程:
- 阶段 1: Task_L 获取锁(不变)
- 阶段 2: Task_H 请求锁被阻塞 → **触发优先级继承!**
- 阶段 3: Task_M 就绪 → **无法再抢占 Task_L!**
- 阶段 4: Task_L 释放锁 → **优先级恢复 + 唤醒 Task_H**
- 阶段 5: 正常调度恢复
- 关键效果对比:
- 优先级继承的核心逻辑:
- 为什么能根治优先级反转?
优先级反转的发生过程
假设系统中有三个任务,按优先级从高到低排列:
- Task_H:最高优先级任务(例如优先级 10)
- Task_M:中优先级任务(例如优先级 20)
- Task_L:最低优先级任务(例如优先级 30)
它们需要访问同一个共享资源(比如一段共享内存或一个硬件外设),该资源必须通过互斥锁(Mutex)保护,确保同一时刻只有一个任务能访问。用 Mutex_A
表示这个锁。
阶段 1:初始状态 & 低优先级任务获取锁
- 时间 t0:
Task_L
(低优先级)处于就绪状态或正在运行。Task_H
和Task_M
可能处于挂起(等待事件)或就绪状态,但尚未被调度器选中运行。- 共享资源目前未被占用。
- 事件:
Task_L
需要访问共享资源。 - 系统动作:
Task_L
成功调用OSMutexPend(Mutex_A, timeout, &err)
。 - 结果:
Task_L
获取了Mutex_A
。Task_L
进入临界区,开始操作共享资源。- 锁
Mutex_A
的状态标记为被Task_L
持有。
阶段 2:高优先级任务就绪并请求锁
- 时间 t1:
Task_L
仍在临界区内运行(尚未完成对共享资源的操作,未释放锁)。- 事件:
Task_H
(高优先级)变为就绪状态(例如,它等待的事件发生了,如定时器到期或收到消息)。
- 系统动作(调度器介入):
- 调度器发现
Task_H
(优先级 10)比当前运行的Task_L
(优先级 30)优先级更高。 - 调度器立即抢占
Task_L
的 CPU。 Task_L
的状态由运行变为就绪。Task_H
的状态由就绪变为运行。
- 调度器发现
- 结果:
Task_H
开始执行。Task_L
暂停在临界区中(它仍然持有Mutex_A
锁)。
阶段 3:高优先级任务请求同一把锁 - 阻塞!
- 时间 t2:
Task_H
正在运行。Task_H
很快也需要访问同一个共享资源。
- 事件:
Task_H
调用OSMutexPend(Mutex_A, timeout, &err)
尝试获取锁。 - 系统动作:
- 内核检查
Mutex_A
的状态,发现它当前正被Task_L
持有。 - 因为锁已被占用,
Task_H
无法立即获取锁。 - 内核将
Task_H
的状态由运行改为阻塞(或称为“挂起”、“等待”),并将其放入Mutex_A
的等待队列中。 Task_H
现在在等待Task_L
释放锁。- 调度器再次被触发,寻找新的就绪任务执行。
- 内核检查
- 关键点:高优先级任务
Task_H
现在被锁卡住了,它在等待低优先级任务Task_L
完成工作并释放锁。
阶段 4:调度器切换回低优先级任务 - 但被中优先级任务插队!
- 时间 t3:
- 调度器发现阻塞的
Task_H
无法运行。 - 调度器查找最高优先级的就绪任务。此时,之前被抢占的
Task_L
(优先级 30)仍然处于就绪状态(它在时间 t1 被Task_H
抢占)。 - 事件:就在调度器即将把 CPU 交还给
Task_L
,让它可以继续执行、完成临界区工作并最终释放锁(从而唤醒Task_H
)时,意想不到的事情发生:Task_M
(中优先级,优先级 20)突然变为就绪状态(例如,它等待的事件发生了)。
- 调度器发现阻塞的
- 系统动作(调度器被迫再次决策):
- 调度器比较当前可运行任务的优先级:
Task_L
(优先级 30 - 就绪)Task_M
(优先级 20 - 新就绪状态)
Task_M
的优先级 (20) 比Task_L
的优先级 (30) 更高。- 调度器选择运行
Task_M
。
- 调度器比较当前可运行任务的优先级:
- 结果:
Task_M
的状态由就绪变为运行。Task_L
再次无法执行!它仍然停留在临界区中,仍然持有Mutex_A
锁。- 阻塞的
Task_H
仍然在等待锁。
阶段 5:中优先级任务长时间运行
- 时间 t4:
Task_M
运行中(执行它自己的任务代码,完全与共享资源无关)。Task_L
保持就绪但无法运行(被Task_M
压着)。Task_H
保持阻塞(等待锁)。
- 问题爆发:
- 此时,本该是系统最高优先级任务的
Task_H
,其执行完全取决于Task_M
何时完成!本质上,Task_M
(中优先级)变相地阻塞了Task_H
(高优先级)。 - 系统的优先级规则被破坏:优先级关系本该是
H > M > L
,但现在实际的执行顺序变成了M > L
,而H
被迫等待L
(而L
又被迫等待M
),最终H
被M
间接阻塞。
- 此时,本该是系统最高优先级任务的
阶段 6:中优先级任务完成 & 低优先级任务终于运行并释放锁
- 时间 t5:
Task_M
完成其工作,进入挂起状态(例如,等待下一个事件)。
- 系统动作:
- 调度器再次被触发,寻找最高优先级就绪任务:此时只有
Task_L
(优先级 30)是就绪状态。 - 调度器选择运行
Task_L
。
- 调度器再次被触发,寻找最高优先级就绪任务:此时只有
- 结果:
Task_L
恢复执行,从它被第二次抢占的地方继续。
- 时间 t6:
Task_L
完成了对共享资源的操作,离开临界区。- 事件:
Task_L
调用OSMutexPost(Mutex_A)
释放Mutex_A
锁。
- 系统动作:
- 内核发现
Mutex_A
的等待队列中有更高优先级的任务Task_H
。 - 内核唤醒
Task_H
(将其状态由阻塞改为就绪)。 - 调度器被立即触发。
- 调度器发现
Task_H
(优先级 10)是最高优先级就绪任务。 - 调度器抢占仍在运行的
Task_L
(优先级 30)。 - 调度器将 CPU 交给
Task_H
。
- 内核发现
- 结果:
Task_H
终于获得锁并进入临界区执行。Task_L
再次被抢占(状态变为就绪)。
核心问题总结:优先级反转的关键时刻在哪里?
关键在于 阶段 4(时间 t3)。此时:
Task_H
被锁阻塞,在等待Task_L
释放锁。- 调度器正准备运行
Task_L
(让它可以去释放锁)。 - 一个完全不相关、但优先级处于
H
和L
之间的任务Task_M
恰好在这个最坏的时机就绪了。
由于调度器的规则是无条件优先执行当前最高优先级的就绪任务,它必须让 Task_M
运行(因为 20 > 30)。这就导致:
- 持有锁的
Task_L
无法继续执行。 - 迫切需要锁的
Task_H
被迫等待的时间被极大地延长了——其等待时间不再是Task_L
剩下的临界区执行时间(本应很短),而是变成了:Task_M
的全部执行时间 +Task_L
剩下的临界区执行时间。
这就是“反转”——原本应该拥有最高执行权力的 Task_H
,其命运被一个比自己优先级还低(但比持锁者高)的任务 Task_M
所主宰,导致系统实时性丧失,关键的高优先级任务可能错过其截止时间 (Deadline)。 火星探路者号频繁重启就是这个原因导致的。解决方案(如优先级继承)就是通过在内核层面动态调整持锁任务的优先级,来防止 Task_M
在这种关键时刻插队。
优先级继承如何解决问题
优先级继承(Priority Inheritance)的核心思想是:当高优先级任务因锁被阻塞时,内核临时提升持有该锁的低优先级任务的优先级,使其免受中优先级任务干扰,从而加速锁的释放。
原始问题场景回顾(未启用优先级继承):
- Task_L(低)持有锁 → Task_H(高)请求锁被阻塞 → Task_M(中)抢占 Task_L → Task_H 被迫等待 Task_M 执行完成
结果:Task_H 的等待时间被无限拉长。
启用优先级继承后的解决流程:
阶段 1: Task_L 获取锁(不变)
- t0: Task_L(优先级 30)获取互斥锁
Mutex_A
,进入临界区。
阶段 2: Task_H 请求锁被阻塞 → 触发优先级继承!
-
t1:
- Task_H(优先级 10)就绪并抢占 Task_L。
- Task_H 请求
Mutex_A
,发现锁被 Task_L 持有。
-
结果:
- Task_L 优先级临时提升至 10(与 Task_H 相同)
- Task_H 进入阻塞态,等待锁释放
✅ 关键点:此时 Task_L 的优先级 = 10,不再是原来的低优先级(30)
阶段 3: Task_M 就绪 → 无法再抢占 Task_L!
- t2:
- Task_M(优先级 20)变为就绪状态
- 调度器决策:
- 当前就绪任务:Task_L(优先级 10),Task_M(优先级 20)
- 比较规则:数值越小优先级越高
→ 优先级 10 > 优先级 20
→ Task_L 继续运行!
- 结果:
- Task_M 无法抢占 Task_L(因其优先级低于 Task_L 的临时优先级 10)
- Task_L 持续占有 CPU 执行临界区代码
阶段 4: Task_L 释放锁 → 优先级恢复 + 唤醒 Task_H
- t3:
- Task_L 完成临界区操作,释放锁
OSMutexPost(Mutex_A)
- Task_L 完成临界区操作,释放锁
- 结果:
- Task_L 优先级恢复为 30
- Task_H(优先级 10)被唤醒,获得锁并开始执行
- 调度器立即切换至 Task_H(因它优先级最高)
阶段 5: 正常调度恢复
- Task_H 执行临界区代码(持有锁)
- 完成后释放锁 → 调度器按正常优先级调度后续任务
关键效果对比:
场景 | Task_H 等待时间 | 系统行为 |
---|---|---|
无优先级继承 | Task_M执行时间 + Task_L剩余时间 | Task_H 被中优先级阻塞 |
启用优先级继承 | 仅 Task_L 剩余执行时间 | Task_M 无法中断锁持有者 |
📊 性能提升:Task_H 的阻塞时间从
T_taskM + T_taskL_remain
缩短为T_taskL_remain
优先级继承的核心逻辑:
-
触发条件:
- 高优先级任务因请求被持有的互斥锁(Mutex) 而阻塞
-
继承动作:
- 内核临时将锁持有者的优先级提升至与该阻塞任务相同
-
防干扰效果:
- 阻止任何优先级低于继承值的任务(如 Task_M)抢占锁持有者
-
恢复机制:
- 锁释放时自动恢复锁持有者的原始优先级
- 唤醒等待队列中优先级最高的任务
为什么能根治优先级反转?
通过动态改写优先级规则打破死循环:
当锁被低优先级任务持有时,内核临时赋予它“高优先级身份”,使中优先级任务失去剥夺权,保证锁持有者能无干扰地快速完成工作,从根本上消除了中优先级任务间接阻塞高优先级任务的可能性。