在可剝奪性的內核中,當任務以獨占方式使用共享資源的時候,會出現低優先級任務高於高優先級任務運行的情況,這種情況叫做優先級反轉,對於實時操作系統而言,這是一場災難,下面我們來說說優先級反轉的典型環境.
我們假設有三個任務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不允許兩個任務有相同的優先級!切記切記