当使用信号量时,不可避免的会遇到"优先级反转“、”死锁“、”链阻塞“这些问题。这里首先介绍下这些问题的产生机理,然后给出解决方法,最后分析下各种解决方法所适用的场景。
大家可以先看看这篇文章,本文只是对该文的一个整理:http://sparkandshine.net/priority-scheduling-inversion-inheritance-ceiling-2
问题产生机理:
1. 优先级反转
如下图所示,当任务P3执行被任务P2打断时,任务P3会长时间占用临界区,直到任务P2执行完。如果在任务P2未执行完时,恰好任务P1到来,但由于P1需要等待已被P3占用的临界区,而P3又在等待P2执行结束,所以最终高优先级的P1反而在等待中优先级的P2。这就形成了优先级反转,这个反转的时间取决于图中T的时长。
2. 临界区死锁
如下图所示,当任务L申请并占用临界区S2时,被任务H打断,此时任务H先申请并占用了临界区S1,之后又申请临界区S2。但S2已被任务L占用,所以任务H需要让出CPU给任务L用。如果此时任务L在释放临界区S2前,又申请了临界区S1,就会导致任务H和任务L互相等待对方释放资源,形成死锁。
3. 链阻塞
如下图所示,由于任务M和任务L先把临界区S1和S2占用了,此时任务H虽然优先级最高,但是却被任务M和任务L频繁阻塞了两次,导致任务执行的不连续。
解决方法:
1. 关闭中断或禁止任务调度
这种方法简单但是粗暴,访问临界区前关闭中断或禁止任务调度,虽然可以解决上述所有问题,但是它会导致其他没有使用临界区的高优先级任务无法及时响应,影响到了系统的实时性。
2. 按序访问临界区资源
导致死锁问题的核心是,未按相同的顺序申请并使用临界区资源。但是按序访问临界区资源无法解决“优先级反转”和“链阻塞”问题。而且需要预先约定所有临界区资源的访问顺序,对于多人协作开发有一定困难。
3. 优先级继承
优先级继承(priority inheritance)是指当高优先级进程(P1)请求一个已经被被低优先级(P3)占有的临界资源时,将低优先级进程(P3)的优先级临时提升到与高优先级进程一样的级别,使得低优先级进程能更快地运行,从而更快地释放临界资源。低优先级进程离开临界区后,其优先级恢复至原本的值。
该策略可以解决优先级反转问题,但无法解决“死锁”和“链阻塞”问题。
4. 优先级天花板
优先级天花板(priority ceiling)是指将申请某资源的任务的优先级提升到可能访问该资源的所有任务中最高优先级任务的优先级。每个临界资源R赋予一个优先级天花板,取所有可能访问临界资源R的所有进程中优先级最高的作为R的优先级天花板。当一个进程获取到临界资源,就将该进程的优先级提升到优先级天花板,这样,该进程不会被其他可能使用该临界资源的进程抢占,从而得到更快执行,更快地释放临界资源。释放临界资源后,恢复优先级。
该方法可解决以上所有问题,但需要预先规划好所有临界区资源的优先级天花板。
总结:
关中断禁调度 | 按序访问临界区资源 | 优先级继承 | 优先级天花板 | |
---|---|---|---|---|
优先级反转导致的时间实时性问题 | √ | X | √ | √ |
链阻塞导致的时间连续性问题 | √ | X | X | √ |
死锁问题 | √ | √ | X | √ |
多人开发是否方便 | √ | X | √ | X |
是否带来了新问题 | 实时性问题 | 无 | 无 | 无 |