优先级反转:
使用实时内核,优先级反转问题是实时系统中出现得最多的问题。设,任务1优先级高于任务2,任务2优先级高于任务3。任务1和任务2处于挂起状态,等待某一事件的发生,任务3正在运行。此时,任务3要使用其共享资源。使用共享资源之前,首先必须得到该资源的信号量(Semaphore)。任务3得到了该信号量,并开始使用该共享资源。由于任务1优先级高,它等待的事件到来之后剥夺了任务3的CPU使用权,任务1开始运行。运行过程中任务1也要使用那个任务3正在使用着的资源,由于该资源的信号量还被任务3占用着,任务1只能进入挂起状态,等待任务3释放该信号量。任务3得以继续运行。由于任务2的优先级高于任务3,当任务2等待的事件发生后,任务2剥夺了任务3的CPU的使用权并开始运行。处理它该处理的事件,直到处理完之后将CPU控制权还给任3。任务3接着运行,直到释放那个共享资源的信号量。直到此时,由于实时内核知道有个高优先级的任务在等待这个信号量,内核做任务切换,使任务1得到该信号量并接着运行。
在这种情况下,任务1优先级实际上降到了任务3 的优先级水平。因为任务1要等,直等到任务3释放占有的那个共享资源。由于任务2剥夺任务3的CPU使用权,使任务1的状况更加恶化,任务2使任务1增加了额外的延迟时间。任务1和任务2的优先级发生了反转。
纠正的方法可以是,在任务3使用共享资源时,提升任务3的优先级。任务完成时予以恢复。任务3的优先级必须升至最高,高于允许使用该资源的任何任务。多任务内核应允许动态改变任务的优先级以避免发生优先级反转现象。然而改变任务的优先级是很花时间的。如果任务3并没有先被任务1剥夺CPU使用权,又被任务2抢走了CPU使用权,花很多时间在共享资源使用前提升任务3的优先级,然后又在资源使用后花时间恢复任务3的优先级,则无形中浪费了很多CPU时间。真正需要的是,为防止发生优先级反转,内核能自动变换任务的优先级,这叫做优先级继承(Priority inheritance)。
UCOS中互斥信号量降解优先级反转的过程:
设mutex已被低优先级的任务3占用。高优先级的任务1提出申请mutex(调用Pend())。在这种情况下:
1) Pend函数注意到高优先级的任务要用这个共享资源,于是将任务3的优先级升高至9(创建mutex时指定,比任何提出申请mutex的任务的优先级都要高),并强制任务调度(由于任务3的优先级升高至9,因此任务3执行),任务3继续使用共享资源。当共享资源使用完后,任务3调用Post函数,释放mutex。
2) Post函数注意到原来占用这个mutex的任务的优先级是被太高的,于是将任务3的优先级恢复到原来水平。
3) Post还注意到有个高优先级的的任务(任务1)正在等待这个mutex,于是将mutex交给这个任务,并做任务切换,让任务1运行。
互斥信号量的组成:
l 一个标志,指示mutex是否可用(OS_MUTEX_AVAILABLE表示可用)。
l 一个优先级,即优先级继承优先级(PIP)。
l 一个等待mutex的任务列表。
创建一个互斥信号量:OSMutexCreate()
互斥信号量创建初的几个成员的值:
OS_EVENT | ||
OSEventType | OS_EVENT_TYPE_MUTEX | |
OSEventCnt | PIP | OS_MUTEX_AVAILABLE |
OSEventPtr | NULL | |
OSEventGrp | 0 | |
OSEventTbl | 0 |
PIP是该函数的参数,指定优先级继承优先级。当发生优先级反转时,将占用该mutex的任务的优先级太高到PIP。
等待(申请)一个互斥信号量:OSMutexPend()
以上参考自:http://blog.csdn.net/yhmhappy2006/article/details/3357335
关键代码解析:
OS_EVENT *OSMutexCreate (INT8U prio, //创建一个互斥信号量
INT8U *perr) //PRIO是任务的优先级 PERR是返回错误代码类型
//OS_ERR_NONE 函数调用成功
//OS_ERR_CREATE_ISR 任务被中断嵌套
//OS_ERR_PRIO_EXIST 任务的优先级继承优先级已经存在 PIP已经存在 无需再创建
//OS_ERR_PEVENT_NULL 没有更多的事件控制块可用。
//OS_ERR_PRIO_INVALID 已经是最低优先级任务了
{
OS_EVENT *pevent;
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0;
#endif
#if OS_ARG_CHK_EN > 0 //参数检测
if (perr == (INT8U *)0) { /* Validate 'perr' */
return ((OS_EVENT *)0); //空指针
}
if (prio >= OS_LOWEST_PRIO) { /* Validate PIP */
*perr = OS_ERR_PRIO_INVALID; //已经是最低优先级任务了
return ((OS_EVENT *)0);
}
#endif
if (OSIntNesting > 0) { /* See if called from ISR ... */
*perr = OS_ERR_CREATE_ISR; /* ... can't CREATE mutex from an ISR */
return ((OS_EVENT *)0); //中断嵌套
}
OS_ENTER_CRITICAL();
if (OSTCBPrioTbl[prio] != (OS_TCB *)0) { /* Mutex priority must not already exist *///已经在等待互斥信号量任务列表中了
OS_EXIT_CRITICAL(); /* Task already exist at priority ... */
*perr = OS_ERR_PRIO_EXIST; /* ... inheritance priority */
return ((OS_EVENT *)0);
}
OSTCBPrioTbl[prio] = OS_TCB_RESERVED; /* Reserve the table entry */
pevent = OSEventFreeList; /* Get next free event control block */
if (pevent == (OS_EVENT *)0) { /* See if an ECB was available */
OSTCBPrioTbl[prio] = (OS_TCB *)0; /* No, Release the table entry */
OS_EXIT_CRITICAL();
*perr = OS_ERR_PEVENT_NULL; /* No more event control blocks *///没有更多的事件控制块可用。
return (pevent);
}
OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr; /* Adjust the free list *///指向下一个时间控制块
OS_EXIT_CRITICAL();
pevent->OSEventType = OS_EVENT_TYPE_MUTEX;
pevent->OSEventCnt = (INT16U)((INT16U)prio << 8) | OS_MUTEX_AVAILABLE; /* Resource is avail. */
//把当前优先级左移8位赋给PIP
pevent->OSEventPtr = (void *)0; /* No task owning the mutex *///没有事件指针 信号量只是2值
#if OS_EVENT_NAME_EN > 0
pevent->OSEventName = "?"; /* Unknown name */
#endif
OS_EventWaitListInit(pevent);
*perr = OS_ERR_NONE;
return (pevent);
}
以上所示,建立互斥信号量的关键就是把当前优先级左移8位赋给PIP。
void OSMutexPend (OS_EVENT *pevent, //等待互斥信号量
INT32U timeout,
INT8U *perr)
{
//OS_ERR_NONE 函数调用成功
//OS_ERR_TIMEOUT 等待超时
//OS_ERR_PEND_ABORT 被中止
//OS_ERR_EVENT_TYPE 没有操作互斥信号量
//OS_ERR_PEVENT_NULL 没有更多的事件控制块可用。
//OS_ERR_CREATE_ISR 任务被中断嵌套
//OS_ERR_PIP_LOWER 还有高优先级的需要调用互斥信号量
//OS_ERR_PEND_LOCKED 任务被锁住
INT8U pip; /* Priority Inheritance Priority (PIP) */
INT8U mprio; /* Mutex owner priority */
BOOLEAN rdy; /* Flag indicating task was ready */
OS_TCB *ptcb;
OS_EVENT *pevent2;
INT8U y;
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0;
#endif
#if OS_ARG_CHK_EN > 0
if (perr == (INT8U *)0) { /* Validate 'perr' */
return; //空指针
}
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
*perr = OS_ERR_PEVENT_NULL; //没有更多的事件控制块可用。
return;
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_MUTEX) { /* Validate event block type */
*perr = OS_ERR_EVENT_TYPE; //没有操作互斥信号量
return;
}
if (OSIntNesting > 0) { /* See if called from ISR ... */
*perr = OS_ERR_PEND_ISR; /* ... can't PEND from an ISR */
return; //被中断嵌套
}
if (OSLockNesting > 0) { /* See if called with scheduler locked ... */
*perr = OS_ERR_PEND_LOCKED; /* ... can't PEND when locked */
return; //被任务嵌套
}
/*$PAGE*/
OS_ENTER_CRITICAL();
pip = (INT8U)(pevent->OSEventCnt >> 8); /* Get PIP from mutex */
/* Is Mutex available? *///信号量可用
if ((INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8) == OS_MUTEX_AVAILABLE) {
pevent->OSEventCnt &= OS_MUTEX_KEEP_UPPER_8;
/* Yes, Acquire the resource */
pevent->OSEventCnt |= OSTCBCur->OSTCBPrio; /* Save priority of owning task */ //将计数器低8为置成占用该mutex的任务(当前任务)的优先级。
pevent->OSEventPtr = (void *)OSTCBCur; /* Point to owning task's OS_TCB */ //在mutex中保存占用信号量的任务:修改该mutex的OSEventPtr ,使其指向当前任务
if (OSTCBCur->OSTCBPrio <= pip) { /* PIP 'must' have a SMALLER prio ... */ //当前任务优先级低于PIP (优先级继承优先级)
OS_EXIT_CRITICAL(); /* ... than current task! */
*perr = OS_ERR_PIP_LOWER;
} else {
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
}
return;
}
mprio = (INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8); /* No, Get priority of mutex owner *///信号量不可用 读取当前占用信号量任务的优先级
ptcb = (OS_TCB *)(pevent->OSEventPtr); /* Point to TCB of mutex owner *///读取占用该信号量的任务块指针
if (ptcb->OSTCBPrio > pip) { /* Need to promote prio of owner?*///如果该任务块优先级低于提出申请该mutex的任务的优先级
if (mprio > OSTCBCur->OSTCBPrio) {
y = ptcb->OSTCBY;
if ((OSRdyTbl[y] & ptcb->OSTCBBitX) != 0) { /* See if mutex owner is ready *///原来的任务已经就绪
OSRdyTbl[y] &= ~ptcb->OSTCBBitX; /* Yes, Remove owner from Rdy ...*/
if (OSRdyTbl[y] == 0) { /* ... list at current prio */
OSRdyGrp &= ~ptcb->OSTCBBitY;
}
rdy = OS_TRUE; //把原来任务的优先级从等待队列中去掉
} else {
pevent2 = ptcb->OSTCBEventPtr; //没有就绪 直接从事件队列清除
if (pevent2 != (OS_EVENT *)0) { /* Remove from event wait list */
if ((pevent2->OSEventTbl[ptcb->OSTCBY] &= ~ptcb->OSTCBBitX) == 0) {
pevent2->OSEventGrp &= ~ptcb->OSTCBBitY;
}
}
rdy = OS_FALSE; /* No */
}
ptcb->OSTCBPrio = pip; /* Change owner task prio to PIP */
#if OS_LOWEST_PRIO <= 63
ptcb->OSTCBY = (INT8U)( ptcb->OSTCBPrio >> 3);
ptcb->OSTCBX = (INT8U)( ptcb->OSTCBPrio & 0x07);
ptcb->OSTCBBitY = (INT8U)(1 << ptcb->OSTCBY);
ptcb->OSTCBBitX = (INT8U)(1 << ptcb->OSTCBX);//修改任务的优先级
#else
ptcb->OSTCBY = (INT8U)((ptcb->OSTCBPrio >> 4) & 0xFF);
ptcb->OSTCBX = (INT8U)( ptcb->OSTCBPrio & 0x0F);
ptcb->OSTCBBitY = (INT16U)(1 << ptcb->OSTCBY);
ptcb->OSTCBBitX = (INT16U)(1 << ptcb->OSTCBX);
#endif
if (rdy == OS_TRUE) { /* If task was ready at owner's priority ...*/
OSRdyGrp |= ptcb->OSTCBBitY; /* ... make it ready at new priority. */
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;//直接进行任务优先级切换
} else {
pevent2 = ptcb->OSTCBEventPtr;
if (pevent2 != (OS_EVENT *)0) { /* Add to event wait list */
pevent2->OSEventGrp |= ptcb->OSTCBBitY;
pevent2->OSEventTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
//次一级的任务
}
}
OSTCBPrioTbl[pip] = ptcb;
}
}
OSTCBCur->OSTCBStat |= OS_STAT_MUTEX; /* Mutex not available, pend current task */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK;
OSTCBCur->OSTCBDly = timeout; /* Store timeout in current task's TCB */
OS_EventTaskWait(pevent); /* Suspend task until event or timeout occurs *///使任务等待事件发生
OS_EXIT_CRITICAL();
OS_Sched(); /* Find next highest priority task ready */
OS_ENTER_CRITICAL();
switch (OSTCBCur->OSTCBStatPend) { /* See if we timed-out or aborted */
case OS_STAT_PEND_OK:
*perr = OS_ERR_NONE;
break;
case OS_STAT_PEND_ABORT:
*perr = OS_ERR_PEND_ABORT; /* Indicate that we aborted getting mutex */
break;
case OS_STAT_PEND_TO:
default:
OS_EventTaskRemove(OSTCBCur, pevent);
*perr = OS_ERR_TIMEOUT; /* Indicate that we didn't get mutex within TO */
break;
}
OSTCBCur->OSTCBStat = OS_STAT_RDY; /* Set task status to ready */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK; /* Clear pend status */
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; /* Clear event pointers */
#if (OS_EVENT_MULTI_EN > 0)
OSTCBCur->OSTCBEventMultiPtr = (OS_EVENT **)0;
#endif
OS_EXIT_CRITICAL();
}
INT8U OSMutexPost (OS_EVENT *pevent)
{
INT8U pip; /* Priority inheritance priority */
INT8U prio;
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0;
#endif
if (OSIntNesting > 0) { /* See if called from ISR ... */
return (OS_ERR_POST_ISR); /* ... can't POST mutex from an ISR */
}
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
return (OS_ERR_PEVENT_NULL);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_MUTEX) { /* Validate event block type */
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
pip = (INT8U)(pevent->OSEventCnt >> 8); /* Get priority inheritance priority of mutex *///从该信号量中获得PIP
/*
从该信号量中获得占用该信号量的任务的优先级。
在OSEventCnt中的低8位保存占用mutex的任务的原始优先级,
不随优先级的提高而改变。
*/
prio = (INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8);
/* Get owner's original priority */
if (OSTCBCur != (OS_TCB *)pevent->OSEventPtr) { /* See if posting task owns the MUTEX */
OS_EXIT_CRITICAL();
return (OS_ERR_NOT_MUTEX_OWNER);
}
/*
确认释放mutex的任务确实是占用mutex的任务自身。
占用/申请mutex的任务的优先级可能是pip(被提高),也可能是原先任务的优先级。
*/
if (OSTCBCur->OSTCBPrio == pip) { /* Did we have to raise current task's priority? */
//将任务优先级修改为原始优先级,并修改相关参数
OSMutex_RdyAtPrio(OSTCBCur, prio); /* Restore the task's original priority */
}
OSTCBPrioTbl[pip] = OS_TCB_RESERVED; /* Reserve table entry */
//若mutex的等待列表不空,唤醒等待列表中最高优先级的任务,并将mutex分配给它
if (pevent->OSEventGrp != 0) { /* Any task waiting for the mutex? */
/* Yes, Make HPT waiting for mutex ready */
/*
唤醒等待列表中最高优先级的任务(从mutex等待列表中删除,使其入就绪表),
清除等待任务的OS_STAT_MUTEX标志,并返回其优先级prio
*/
prio = OS_EventTaskRdy(pevent, (void *)0, OS_STAT_MUTEX, OS_STAT_PEND_OK);
pevent->OSEventCnt &= OS_MUTEX_KEEP_UPPER_8; /* Save priority of mutex's new owner */
//将mutex分配给新任务:置OSEventCnt为prio
pevent->OSEventCnt |= prio;
//在mutex中保存占用信号量的任务
pevent->OSEventPtr = OSTCBPrioTbl[prio]; /* Link to new mutex owner's OS_TCB */
if (prio <= pip) { /* PIP 'must' have a SMALLER prio ... */
OS_EXIT_CRITICAL(); /* ... than current task! */
OS_Sched(); /* Find highest priority task ready to run */
return (OS_ERR_PIP_LOWER);
} else {
OS_EXIT_CRITICAL();
OS_Sched(); /* Find highest priority task ready to run */
return (OS_ERR_NONE);
}
}
pevent->OSEventCnt |= OS_MUTEX_AVAILABLE; /* No, Mutex is now available */
pevent->OSEventPtr = (void *)0;
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}