优先级调度、优先级反转、优先级继承、优先级天花板

本文介绍优先级调度产生的优先级反转问题及解决反转问题的方法,包含禁止中断、不可抢占、优先级继承、优先级天花板。

1. 优先级调度和优先级反转

高优先级优先调度。根据进程紧急程度、重要性赋予不同优先级(可以静态,也可以动态调整),进程调度时,从就绪队列中选取优先级最高的进程运行。看似很简单,然而,当多个进程访问同一个临界资源时,可能会出现优先级反转问题。

1.1 优先级反转

举个例子,系统三个进程的伪代码如下(PV指信号量,CS指临界区):

P1:   P; CS; V
P2:   program
P3:   P; CS; V

这三个进程到达顺序为P3, P2, P1(如箭头所示),他们的优先级为P1>P2>P3,如图1所示。

进程到达及优先级示意图
图1 进程到达及优先级示意图

假设采用抢占式优先级调度算法,可以作出进程执行顺序,如下:


图2 优先级反转

进程P3先到达,先运行,进程进入临界区;此时P2到达,P2优先级高于P3,抢占P3获得CPU执行权;P2运行一段时间,恰巧P1到达,P1优先级高于P2,抢占P2获得CPU执行权;P2运行到临界区,企图进入临界区,然而此时临界区被P3占用,P1不得以只能进入阻塞状态;现在CPU空闲,从[P2, P3]选取高优先级进程(即P2)运行(这里,低优先级进程P2执行,而高优先级P1只能干瞪眼,这种现象称为优先级反转);P2运行完,轮到P3,P3运行完,释放临界区,P1进入就绪态,获得CPU执行权。

透过这个例子,我们可以发现优先级反转出现在一个高优先级任务等待访问一个被低优先级任务正在使用的临界资源,此时,高优先级任务阻塞了;同时,该低优先级任务被一个次高优先级的任务所抢先(preempt),导致低优先级任务得不到执行,无法及时地释放该临界资源。可见,高优先级任务和次高优先级任务的优先级反转了。

解决优先级反转方法有多种,包括禁止中断、禁止抢占、优先级继承、优先级天花板等。在介绍解决方法之前,先看一个真实的例子,放松下。

1.2 一个真实的例子

火星探路者(The Mars Pathfinder)是第一个到达火星的无人飞行器(于1996年12月份离开地球,1997年3月登陆火星)。火星探路者号上运行的操作系统是VxWorks(一款嵌入式实时操作系统)。

The Mars Pathfinder
图3 美国火星探路者

探路者有一个消息总线(information bus),用于在不同部件之间传递信息,该消息总线属于临界资源,需要互斥访问。

现在故事开始,

  • 高优先级进程P1:消息总线进程,通过消息总线检索数据(retrieve published data)
  • 中优先级进程P2:通信进程,运行时间长
  • 低优先级进程P3:气象数据收集进程,将收集到的气象数据放到消息总线上,运行频率不高

试想这么一种情况,P3获取消息总线(临界资源)之后,被P2抢占,长进程P2被P1抢占,但P1因临界资源被P3拿着不得以进入阻塞状态,此时P2捡到大便宜,轮到他运行。丧心病狂的是,P2是个长进程,运行时间长。那么,P1长时间得不到运行,系统中看门狗由此判定系统出现重大错误(这可是实时操作系统),于是重启系统,导致数据丢失。

更多关于火星探路者上优先级反转有趣内容,见What really happened on Mars Rover Pathfinder

2. 不可抢占和禁止中断

2.1 不可抢占

若采用非抢占(non-preemptive),就不会存在优先级反转问题。非抢占方式是指某一进程被调度运行后,除非由于它自身的原因不能运行,否则一直运行下去。

Non-preemptive multitasking is a style of computer multitasking in which the operating system never initiates a context switch from a running process to another process.

Instead, processes voluntarily yield control periodically or when idle or logically blocked in order to enable multiple applications to be run concurrently.

接上例,在不可抢占方式下,进程的执行顺序如下图所示。

不可抢占
图4 不可抢占,不会有优先级反转

P3一口气运行完,不会被P2抢占,此时P1还没到来,运行P2,最后才是运行P1。

尽管这是一个解决方法,但背离了优先级初衷,并不是一个好的方案。

2.2 禁止中断

关闭中断,进入临界区;退出临界区,开中断。其实,这是一种避免临界资源被交叉访问的一种解决方法。延续上述的例子,使用禁止中断方法,进程的执行顺序示意图如下:

禁止中断
图5 禁止中断情况下进程执行顺序

尽管P2在P3执行临界区到达,但因为禁止中断,期间不会发生进程切换,直到P2退出临界区(此时,关中断),P2可以抢占P3;P2运行了一段时间,高优先级P1到来,抢占P2;P1开始运行,因为临界资源可用,P1可以一口气执行到结束。

禁止中断类似于不可抢占方式,只是粒度不像不可抢占那么极端。但有可能存在误杀,试想P1没有跟P3竞争临界区。

3. 优先级继承

优先级继承(priority inheritance)是指当高优先级进程(P1)请求一个已经被被低优先级(P3)占有的临界资源时,将低优先级进程(P3)的优先级临时提升到与高优先级进程一样的级别,使得低优先级进程能更快地运行,从而更快地释放临界资源。低优先级进程离开临界区后,其优先级恢复至原本的值。

接着上面的例子,采用优先级继承策略,三个进程执行顺序如下:

优先级继承
图6 优先级继承

3.1 Linux

Linux实现了优先级继承策略。

#include <pthread.h>

int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *restrict attr, int *restrict protocol);
int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol);

参数protocol取值可以是(在pthread.h定义了):

  • PTHREAD_PRIO_NONE
  • PTHREAD_PRIO_INHERIT
  • PTHREAD_PRIO_PROTECT

详情见:pthread_mutexattr_getprotocol(3) – Linux man page

3.2 存在问题

相对于不可抢占和禁止中断,优先级继承似乎好得多,然而,优先级继承依然有问题。

(1)潜在死锁

以下图为例[3],高优先级任务T1请求被低优先级任务T2占有的临界资源S2,进入阻塞状态。尽管把T2优先级提高到与T1同样的水平,T2运行一段时间,当请求被T1占有的临界资源S1时,也会进入阻塞状态,此时死锁形成。

Potential deadlock
图7 潜在死锁

(2)链阻塞chained blocking

以下图为例[3],高优先级任务T1请求临界资源S1;中优先级任务T2优先级被提升,获得CPU执行权,释放S1;T1继续往前执行,但当请求临界资源S2时,低优先级任务T3优先级被提升,T3释放S2,T1得以继续执行。在这个例子中,T1被阻塞了两次。更极端例子,T1会被阻塞很多次,这个叫chained blocking。

Chained blocking
图8 链阻塞chained blocking

幸运的是,接下来要介绍的优先级天花板,可以解决上述这两个问题。

4. 优先级天花板

优先级天花板(priority ceiling)是指将申请某资源的任务的优先级提升到可能访问该资源的所有任务中最高优先级任务的优先级。

每个临界资源R赋予一个优先级天花板(priority ceiling),取所有可能访问临界资源R的所有进程中优先级最高的作为R的优先级天花板。举例,进程[P1, P2, P3]都会访问临界资源R,那么,R的优先级天花板为1(跟最高优先级P1一样)。

当一个进程获取到临界资源,就将该进程的优先级提升到优先级天花板,这样,该进程不会被其他可能使用该临界资源的进程抢占,从而得到更快执行,更快地释放临界资源。释放临界资源后,恢复优先级。

举例,如下图所示,进程[P1, P3]会访问临界资源S1,因此S1的优先级天花板为1;同理可得,S2的优先级天花板也为1。接下来看进程的执行顺序,P3成功占有临界资源S1,此时P3优先级被提升至1,顺利执行完临界区的代码,恢复优先级;P2抢占P3,同理,P2优先级提升至1,P2也可以执行完临界区的代码;P1抢占P2,此时,临界资源S1和S2皆已释放,P1可以一口气执行到底。可见,这里不存在链阻塞问题(chained blocking)。

优先级天花板
图9 优先级天花板

优先级天花板的主要思想是认为系统中的资源都很宝贵,为了让占有临界资源的进程尽快释放资源,系统将进程优先级提升到优先级天花板,让进程不会被其他可能使用该临界资源的进程抢占。但这样的策略有一个隐含前提,事先知道临界资源的优先级天花板,这在通用操作系统(比如Linux)很难做到,但在实时操作系统倒是有可能,μC/OS-II 2.52就实现了优先级天花板。

5. Random boosting

Random boosting is a strategy used by the scheduler in Microsoft Windows to avoid deadlock due to priority inversion. Ready threads holding locks are randomly boosted in priority and allowed to run long enough to exit the critical section.

6. Avoid blocking

Because priority inversion involves a low-priority task blocking a high-priority task, one way to avoid priority inversion is to avoid blocking, for example by using non-blocking synchronization or read-copy-update.

参考资料:

[1] What really happened on Mars Rover Pathfinder

[2] Priority Ceiling Protocol – GeeksforGeeks

[3] ResourceAccessProtocols.pdf

[4] Priority inversion – Wikipedia

发表评论

电子邮件地址不会被公开。 必填项已用*标注