这篇文章说明一条嵌入式实时系统(RTOS)中一条普遍存在的bug, 包括ucos, rt-thread中都存在本文要说的问题.说是bug,其实也没有那么严重,因为只有在比较苛刻的几个条件同时满足的时候,它才会表现为一个bug.下面来分析一下.
首先从优先级反转讲起.
优先级反转
在多任务系统中,不同任务之间存在共享资源,操作系统一般会提供mutex等同步机制来保证数据同步.有时候低优先级的任务已经持有了某个共享资源,因此,如果一个高优先级的任务想要访问该共享资源,需要等待低优先级的任务释放该资源,这种现象被称为优先级翻转(Priority Inversion).下图是一个发生优先级反转的例子,系统一开始任务 C 执行 P(S) 申请访问共享资源 S 并获得 S 的占有权,然后高优先级任务 A 被创建并调度执行,此时任务 A 也需要访问资源 S,但是因为 C 已经占有了资源 S,所以 A 被迫等待资源 S 直到任务 C执行 V(S) 释放资源 S。除非能保证所有任务在访问共享资源时,共享资源都未被其他低优先级的任务占有,否则为了保证数据同步,优先级反转的现象很难避免。一般能够接受系统出现下图中所示的情况,因为任务对共享资源的访问一般都会在较短时间结束,下图中任务 A 虽然在等待一个低优先级的任务 C,但等待的时间是有限的.
下图给出了一个更糟糕的优先级反转的例子,假设任务优先级 A>B>C,一开始任务 C 执行 P(S) 申请访问共享资源 S 并获得 S 的占有权;然后任务 A 被创建并调度执行,此时任务 A 也需要访问资源 S,但是因为 C 已经占有了资源S,所以 A 被迫等待资源 S 直到任务 C 执行 V(S) 释放资源 S;但是 C 在执行时,任务 B 被创建并调度执行,此时任务 A 不仅仅需要等待任务 C 还需要等待任务 B,但任务 B 不在临界区中,所以执行时间是不确定的。这种情况对于实时系统来说是致命的,因为高优先级的任务 A 可能永远无法被调度执行,这种高优先级任务可能无限期等待低优先级任务执行的现象被称为无边界优先级反转(unbounded-priority-inversion)。
上面第一幅图是有边界的优先级反转,下面一幅图是无边界优先级反转.
避免优先级反转的两种常见方法是:优先级继承协议(Priority Inheritance Protocol, PIP)和优先级上限协议(Priority Ceiling Protocol, PCP),其中优先级上限协议又被称为优先级天花板协议。两种方法的前提都是优先级可改变,而且是可以动态改变的。
优先级上限协议:
为每个mutex设置一个“优先级顶”,“优先级顶”定义为要调用该mutex的所有任务中最高优先级, 一个任务要想对共享资源操作开始一个新的临界区时,它的优先级必须严格高于当前其他试图获取该mutex的任务的“优先级顶”,否则它将被具有中等优先级的任务所阻塞。每个信号量的“优先级顶”是在任务集执行之前通过预分析得到的。
优先级继承协议:
优先级继承协议(Priority Inheritance Protocol, PIP)的基本思想是当更高优先级的进程A被进程B阻塞时,B暂时继承A的优先级,这将防止中间优先级的进程抢占进程B使高优先级进程A的阻塞时间延长。
优先级继承协议应对优先级反转
优先级天花板协议应对优先级反转
优先级天花板和优先级继承的异同
区别:
优先级继承需要将资源owner任务的低优先级提升为和试图获取资源的任务的同样的高优先级,所以,对于实现优先级继承的系统来说,支持任务的同等优先级是必要条件.
所以,在不支持同等优先级的系统中,无法实现优先级继承,只能实现优先级天花板机制.这样的系统有ucosii等等.
所以,区别1,优先级继承只能实现在支持同等优先级的系统中,而且优先级天花板则与系统能否支持同等优先级无关.
联系:
优先级继承是优先级天花板的特殊情况,当天花板优先级和owner任务的优先级相同时,优先级天花板就变成了优先级继承.
UCOSII优先级天花板的实现:
ucosii中mutex相关实现接口有:
-
OS_EVENT *OSMutexCreate (
INT8U prio, INT8U *perr);
-
OS_EVENT *OSMutexDel (
OS_EVENT *pevent, INT8U opt, INT8U *perr)
;
-
void OSMutexPend (
OS_EVENT *pevent, INT16U timeout, INT8U *perr)
;
-
INT8U OSMutexPost (
OS_EVENT *pevent);
-
INT8U OSMutexQuery (OS_EVENT *pevent, OS_MUTEX_DATA *p_mutex_data);
-
static void OSMutex_RdyAtPrio (OS_TCB *ptcb, INT8U prio)
;
-
BOOLEAN OSMutexAccept (
OS_EVENT *pevent, INT8U *perr)
OS_EVENT结构体定义:
-
388
#if (OS_EVENT_EN) && (OS_MAX_EVENTS > 0)
-
389
typedef
struct
os_event {
-
390
INT8U
OSEventType;
/*
Type
of
event
control
block
(see
OS_EVENT_TYPE_xxxx)
*/
-
391
void
*OSEventPtr;
/*
Pointer
to
message
or
queue
structure
*/
-
392
INT16U
OSEventCnt;
/*
Semaphore
Count
(not
used
if
other
EVENT
type)
*/
-
393
#if OS_LOWEST_PRIO <= 63
-
394
INT8U
OSEventGrp;
/*
Group
corresponding
to
tasks
waiting
for
event
to
occur
*/
-
395
INT8U
OSEventTbl[
OS_EVENT_TBL_SIZE]
;
/*
List
of
tasks
waiting
for
event
to
occur
*/
-
396
#else
-
397
INT16U
OSEventGrp;
/*
Group
corresponding
to
tasks
waiting
for
event
to
occur
*/
-
398
INT16U
OSEventTbl[
OS_EVENT_TBL_SIZE]
;
/*
List
of
tasks
waiting
for
event
to
occur
*/
-
399
#endif
-
400
-
401
#if OS_EVENT_NAME_SIZE > 1
-
402
INT8U
OSEventName[
OS_EVENT_NAME_SIZE]
;
-
403
#endif
-
404 }
OS_EVENT;
-
405
#endif
OS_EVENT *OSMutexCreate (INT8U prio, INT8U *perr)中,第一个参数prio表示的访问互斥量时使用的优先级。换句话说,当信号量被获取时,一个更高优先级的任务尝试获取该信号量,那么拥有该信号量的任务的优先级将被提升到该优先级。这里需要指定一个优先级,其优先级高于任何竞争互斥锁的任务。其值被记录再pevent->OSEventCnt中.
void OSMutexPend (OS_EVENT *pevent, INT16U timeout, INT8U *perr)函数中,如果mutex当前处于有资源的状态,则无论任务的优先级是高还是低,都不改变当前优先级(因为不存在其它优先级的竞争,优先级天花板协议就可以推迟生效,假如竞争不存在,优先级提升就没有必要了).获取后,将owner线程记录再pevent->OSEventPtr中.并且抹去avaliable状态,变为owner任务的优先级.这里还要进行一项检查,确保PIP优先级是所有会竞争mutex的任务中最高的.
如果mutex当前无资源,则调整owner的优先级到pip级.
这是ucosii的做法,看似没有问题,实际上,这里存在一个bug.
UCOSII Mutex嵌套使用时出现的 bug
下图展示了一个UCOSII Mutex在嵌套使用时出现的Bug,连PIP机制都救不了.
图中包含 4 个任务 A、B、C、D 以及两个互斥信号量 S1、S2。用户设置的原始优先级满足下述关系:PrD < PrC < PrS2 < PrB < PrA < PrS1。假设一开始任务 A、B、C 被延迟并且正在等待时钟中断唤醒,D 是唯一正在运行的任务。首先任务 D 获取信号量S2,然后 C 被唤醒并抢占了 D。C 接着获取了信号量 S1, 然后尝试获取 S2,但因为 S2 已经被 D 占有,所以 C 会被阻塞,因为 PrD < PrC,所以根据协议将D 的优先级提升到 PrS2,并且 D 被调度执行。然后任务 A 被唤醒,因为 A 的优先级最高,所以 A 抢占了 D。此时 A 尝试获取被 C 占有的 S1 ,所以 A 被阻塞,并且 C 的优先级被提升到 PrS1。最后任务 B 被唤醒并执行,但此时已经发生了无边界优先级反转,因为任务 A 可能无限期等待任务 B。并且从图中可以看出,此时任务 B 不占有任何资源,并且原始优先级低于 A。图中括号内的二元组表示该任务当前优先级和状态.
从就绪队列的角度看,最高优先级的任务A的优先级,并没有传染给就绪队列的其他任务,因为虽然任务C受到了A的影响,被提升了优先级,但是由于任务C睡眠在了S2上.S1的优先级并没有间接影响到S2的owner任务,也就是D.导致无边界优先级反转的情况再次出现.
这个bug,在rt-thread操作系统中同样存在.
结束!
转载:https://blog.csdn.net/tugouxp/article/details/116428458