在可剥夺性的内核中,当任务以独占方式使用共享资源的时候,会出现低优先级任务高于高优先级任务运行的情况,这种情况叫做优先级反转,对于实时操作系统而言,这是一场灾难,下面我们来说说优先级反转的典型环境.
我们假设有三个任务a,b,c,a优先级高于b,b优先级高于c,a和c都需要访问一个共享资源s,保护该资源的信号量为互斥信号量,
假设当前任务c申请了信号量访问s,还没有释放,此时任务a开始运行,那么a就会剥夺c的运行而运行a,当a去访问资源s的时候,因为得不到信号量,所以必须释放以等待信号量,任务c得以重新运行,到这里流程都是正常的,信号量的设计也是为了满足这个功能,但是,当任务c在运行并准备释放信号量的时候,任务b开始运行,那么任务b就要剥夺任务c的运行,这个时候系统就只有b在运行,而a能打断b的运行但是需要信号量,可是c优先级比较低得不到运行,这样,a就只能等到b运行完主动释放使用权才能得到运行了.
到这里问题就发生了,优先级比较高的a在优先级比较低的b运行的时候无法抢断,可剥夺性内核却剥夺不了,系统故障,在这种故障极大地降低了系统的实时性
以上说的情况就是操作系统的优先级反转
而ucos为了解决这种问题,在互斥信号量中引入了优先级提升的方法,他的基本思想是:让当前获得互斥信号量的任务的优先级短暂提升到系统可以接受的最大优先级,尽量让该任务快速的完成并释放信号量,释放之后在恢复为任务原来的优先级别.
原理说完了,接下来我们来看看代码,之前已经说过互斥信号量的部分实现,那部分不再赘述,集中看优先级提升的部分,优先级的提升我们可以猜测应该在一个任务在获取了信号量之后完成的,那也就是说,应该在ospendxxx函数里面
查看OSMutexPend函数,发现其中果然有玄机,如下
pip = (INT8U)(pevent->OSEventCnt >> 8u);
if ((INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8) == OS_MUTEX_AVAILABLE) {
pevent->OSEventCnt &= OS_MUTEX_KEEP_UPPER_8;
pevent->OSEventCnt |= OSTCBCur->OSTCBPrio;
pevent->OSEventPtr = (void *)OSTCBCur;
if (OSTCBCur->OSTCBPrio <= pip) {
OS_EXIT_CRITICAL();
*perr = OS_ERR_PIP_LOWER;
} else {
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
}
Pip变量是保存在OSEventCnt中的,当我们创建信号量的时候,就会给定这个值,这个值也就是系统能够将等待该互斥信号量的任务提升的最高优先级
当一个任务请求信号量的时候,如果有信号量空余,将当前请求信号量的任务的优先级放到OSEventCnt的低八位中,
if (OSTCBCur->OSTCBPrio <= pip)
如果当前请求信号量的任务的优先级高于最高提升优先级(数值上低于),那直接运行,没必要提升优先级,否则的话,就要进行下面的操作
mprio = (INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8);
ptcb = (OS_TCB *)(pevent->OSEventPtr);
if (ptcb->OSTCBPrio > pip) {
if (mprio > OSTCBCur->OSTCBPrio) {
y = ptcb->OSTCBY;
if ((OSRdyTbl[y] & ptcb->OSTCBBitX) != 0u) {
OSRdyTbl[y] &= (OS_PRIO)~ptcb->OSTCBBitX;
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
}
rdy = OS_TRUE;
这一段的意思就是当优先级低于最高可提升优先级的时候,将系统就绪表中的原来的ready标志清除掉,接下来
ptcb->OSTCBPrio = pip;
ptcb->OSTCBY = (INT8U)( ptcb->OSTCBPrio >> 3u);
ptcb->OSTCBX = (INT8U)( ptcb->OSTCBPrio & 0x07u);
ptcb->OSTCBBitY = (OS_PRIO)(1uL << ptcb->OSTCBY);
ptcb->OSTCBBitX = (OS_PRIO)(1uL << ptcb->OSTCBX);
if (rdy == OS_TRUE) {
OSRdyGrp |= ptcb->OSTCBBitY;
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
} else {
pevent2 = ptcb->OSTCBEventPtr;
if (pevent2 != (OS_EVENT *)0) {
pevent2->OSEventGrp |= ptcb->OSTCBBitY;
pevent2->OSEventTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
}
}
OSTCBPrioTbl[pip] = ptcb;
将当前任务的优先级切换成提升优先级,并把快速访问就绪表的元素的数据改变,同时修改系统就绪表,将提升优先级的任务的新优先级在任务就绪表中设置成就绪,最后,在tcb表中对应pip的位置,设置为提升了优先级的任务的tcb.
这样,任务的优先级就被提升了,系统下一次被调用的时候,就会按照被提升了优先级的任务的新优先级来进行调度.
既然优先级能被提升,那么也应该要能被降下来,而这个降下来应该需要依靠ospostxxx在释放信号量的时候执行,我们查看OSMutexPost代码,可以看到
if (OSTCBCur->OSTCBPrio == pip) { OSMutex_RdyAtPrio(OSTCBCur, prio);
}
也就是说,当释放信号量的任务的优先级为等于互斥信号量最高可提升优先级的时候,需要通过OSMutex_RdyAtPrio函数来将任务的优先级恢复,prio的来源是从事件中获取的优先级,大家还记得记得提升之前的优先级保存到了哪里?就是保存在这里, OSEventCnt的低八位
pevent->OSEventCnt |= OSTCBCur->OSTCBPrio;
恢复优先级的函数为OSMutex_RdyAtPrio,核心代码为
y = ptcb->OSTCBY;
OSRdyTbl[y] &= (OS_PRIO)~ptcb->OSTCBBitX;
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
}
ptcb->OSTCBPrio = prio;
OSPrioCur = prio;
#if OS_LOWEST_PRIO <= 63u
ptcb->OSTCBY = (INT8U)((INT8U)(prio >> 3u) & 0x07u);
ptcb->OSTCBX = (INT8U)(prio & 0x07u);
#else
ptcb->OSTCBY = (INT8U)((INT8U)(prio >> 4u) & 0x0Fu);
ptcb->OSTCBX = (INT8U) (prio & 0x0Fu);
#endif
ptcb->OSTCBBitY = (OS_PRIO)(1uL << ptcb->OSTCBY);
ptcb->OSTCBBitX = (OS_PRIO)(1uL << ptcb->OSTCBX);
OSRdyGrp |= ptcb->OSTCBBitY;
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
OSTCBPrioTbl[prio] = ptcb;
流程与之前提权其实是类似的,只是一个换成高优先级一个换成低优先级
先将高优先级的任务的任务就绪表中对应的位清除,然后重新设置任务的原始优先级以及当前任务优先级(释放信号量和申请信号量的是同一个任务),然后设置快速访问就绪任务表的数据元素,重新设置任务就绪表,最后将tcb数组中对应原始优先级的数据指针设置到指向任务tcb,这样就实现了任务的恢复.
到这里,就说明白了互斥信号量的优先级提升流程,要注意一点,如果使用互斥信号量的优先级提升,那么那个可提升最高优先级必须不能对应有相应的用户任务,因为ucos不允许两个任务有相同的优先级!切记切记