定义:在可剥夺型内核中,当任务以独占方式使用共享资源时,会出现低优先级任务先于高优先级任务而被运行的现象,这就是所谓的任务优先级反转。
为了找到杜绝任务优先级反转现象的方法,下面就对优先级的反转现象做一个详细的分析。图5-1描述的任务A、B、C三个任务的运行情况。其中任务A的优先级高于任务B,任务B的优先级高于任务C。任务A和任务C都要使用同一个共享资源S,而用于保护该资源的信号量在同一时间只能允许一个任务以独占的方式对该资源进行访问,即这个信号量是一个互斥信号量。
图5-1 优先级反转示意图
现在,假设任务A和任务B都在等待与各自任务的相关的事件发生而处于等待状态,而任务C则正在运行,且在时刻取得信号量并开始访问共享资源S。
如果任务C使用共享资源S过程中的时刻,任务A等待的事件已经到来,那么由于任务A的优先级高于任务C的优先级别,所以任务A就剥夺任务C的CPU使用权而进入运行状态,而是任务C中止任务,这样任务C就失去了释放信号量的机会。如果任务A在运行中的
时刻又要访问共享资源S,但是由于任务C还未释放信号量,因此任务A只好等待,以使任务C可以继续使用共享资源S。
以上的过程都是正常的,是应用程序设计者意料之中的事件。问题是,如果在任务C继续使用共享资源S过程中的时刻,任务B所等待的事件也来临,由于任务B的优先级高于任务C的优先级,任务B当然要剥夺任务C的CPU使用权而进入运行状态,而任务C只好等待。这样,任务A只有当任务B运行结束,并使任务C继续运行且释放了信号量的
时刻之后,才能获得信号量而得以重新运行。
综上所述,任务优先级别低的任务B反而先于任务优先级别高的任务A运行了。实际上看似乎任务B的优先级别高于任务了。之所以出现上述优先级反转的现象,是因为一个优先级别较低的任务在获得信号量使用共享资源期间,被既有较高优先级别的任务所打断而不能释放信号量,从而使正在等待这个信号量的更高级别的任务因得不到信号量而被迫处于等待状态,这个等待期间,就让优先级别低于它而高于占据信号量的任务的任务先运行了。显然,如果这种优先级别介于使用信号量的两个任务优先级别中间的中等优先级别任务较多,则会极大地恶化高优先级别任务的运行环境,是实时系统所无法容忍的。
例子
OS_EVENT *Sem;
void FirstTask(void *p_arg)
{
INT8U err=0;
p_arg = p_arg;
while (1)
{
OSTimeDlyHMSM(0, 0, 0, 200);
{
OS_Printf("FirstTask Sem_Pend\n");
OSSemPend(Sem, 0, &err);
OS_Printf("FirstTask Run\n");
OSSemPost(Sem);
}
OSTimeDlyHMSM(0, 0, 0, 200);
}
}
void TwoTask(void *p_arg)
{
p_arg = p_arg;
while (1)
{
OS_Printf("Two Task!\n");
OSTimeDlyHMSM(0, 0, 0, 300);
}
}
void ThreeTask(void *p_arg)
{
INT8U err;
INT32U time;
p_arg = p_arg;
while (1)
{
OSSemPend(Sem, 0, &err);
OS_Printf("Three Run\n");
for (time = 0; time < 20000000; time++)
{
OS_Sched(); //使用共享资源
}
OSSemPost(Sem);
OSTimeDlyHMSM(0, 0, 1, 0);
}
}
程序的运行结果如图所示,解决办法之一是,使获得信号量任务的优先级在使用共享资源期间暂时提升到所有任务最高优先级别的高一个级别上,以使该任务不被其他任务所打断,从而能尽可能的使用完共享资源并释放信号量,然后在释放信号量之后,在恢复该任务原来的优先级别。
任务可以用互斥型信号量来实现对其共享资源的独占式处理。为了解决任务在使用独占式资源出现优先级反转的问题,互斥型信号量除了具有普通信号量的机制外,还有其他一些特性。
uC/OS-II仍然用事件控制块来描述一个互斥型信号量,如图5-2所示。
图5-2 互斥型信号量的结构
从上图可知,成员OSEventCnt被分成了低8位和高8位两部分,低8位用来存放信号(该值为0xff时,信号量为有效,否则信号量为无效);高8位用来存放为了避免优先级反转现象而要提升的优先级别prio.
创建互斥型信号量的函数OSMutexCreate()。该函数的原型如下:
OS_EVENT * OSMutexCreate(
INT8U prio, //优先级别
INT8U *err
)
函数OSMutexCreate()从空事件链表获取一个事件控制块,把成员OSEventType赋予常数OS_EVENT_TYPE_MYTEX,以表明这是一个互斥型信号量;然后再把成员OS_EventCnt的高8位赋予prio(提升的优先级别),低8位赋予常数OS_MUTEX_AVAILE_BLE(该常数值为OXFFFF)的低8位(0XFF),以表明信号量尚未被任何任务占用,处于有效状态.
当任务需要访问一个独占式共享资源时,就要调用函数OSMutexPend()来请求管理这个资源的互斥型信号量。如果信号量有效,则意味着目前尚无任务占用资源,于是任务可以继续运行并继续对该资源进行访问;否则进入等待状态,直至占用这个资源的其他任务释放了该信号量.
为防止任务因得不到信号而处于长期的等待状态,函数OSMutexPend()允许用参数timeout设置一个等待时间的限制。当任务等待的时间超出该时间限制值时,可以结束等待状态,从而进入就绪态。其函数原型如下:
void OSMutexPend(
OS_EVENT *pevent, //互斥型信号量指针
INT16U timeout, //等待时限
INT8U *err //错误信息
)
任务也可以调用函数OSMutexAccept()无等待地请求一个互斥型信号量.该函数的原型如下:
INT8U OSMutexAccept(
OS_EVENT * pevent,
INT8U *err
)
该函数原型如下:
INT8U OSMutexPost(
OS_EVENT *pevent
)
调用函数OSMutexQuery(
OS_EVENT * pevent,
OS_MUTEX_DATA *pdata //存放互斥型信号量状态的结构
)
函数被调用后,在pdata指向的结构中存放了互斥型信号量的相关信息。OS_MUTEX_DATA的结构如下:
typedef struct{
INT8U OSEventTbl[OS_EVENT_TBL_SIZE];
INT8U OSEventGrp;
INIT8U OSValue;
INT8U OSOwnerPrio;
INT8U OSMutexPIP;
}OS_MUTEX_DATA;
该函数的原型如下:
OS_EVENT * OSMutexDel(
OS_EVENT *pevent,
INT8U opt, //删除的方式,参照信号量
INT8U *err
)