uCOS-II任務管理之改變任務的優先級
在uCOS-II 裡,任務的優先級也是是可以修改的。優先級翻轉問題是可以通過改變任務優先級解決。
那什麼是優先級翻轉問題呢?
所謂優先級翻轉問題(priority inversion)即當一個高優先級任務通過信号量機制通路共享資源時,該信号量已被一低優先級任務占有,而這個低優先級任務在通路共享資源時可能又被其它一些中等優先級任務搶先,是以造成高優先級任務被許多具有較低優先級任務阻塞,實時性難以得到保證。
如果在通路共享資源時,恰當地修改任務的優先級就可以解決優先級翻轉問題了。
但改變任務的優先級也是很花時間的,如果不發生優先級翻轉而提升了任務的優先級,釋放資源後又改回原優先級,則無形中浪費了許多CPU時。在mcu21項目中并沒有改變任務的優先級。
改變任務的優先級的操作也是很簡單的。主要是調用uCOS-II 系統函數OSTaskChangePrio (INT8U oldprio, INT8U newprio); ,它需要兩個參數,一個任務原來的優先級,一個是任務改變後的優先級。
下面深入地解剖下OSTaskChangePrio (INT8U oldprio, INT8U newprio)的實作過程:
系統函數OSTaskChangePrio()
INT8U OSTaskChangePrio (INT8U oldprio, INT8U newprio)
{
OS_TCB *ptcb;
OS_EVENT *pevent;
INT8U x;
INT8U y;
INT8U bitx;
INT8U bity;
if ((oldprio >= OS_LOWEST_PRIO && oldprio != OS_PRIO_SELF) || //(1)
newprio >= OS_LOWEST_PRIO) {
return (OS_PRIO_INVALID);
}
OS_ENTER_CRITICAL();
if (OSTCBPrioTbl[newprio] != (OS_TCB *)0) { //(2)
OS_EXIT_CRITICAL();
return (OS_PRIO_EXIST);
} else {
OSTCBPrioTbl[newprio] = (OS_TCB *)1; //(3)
OS_EXIT_CRITICAL();
y = newprio >> 3; //(4)
bity = OSMapTbl[y];
x = newprio & 0x07;
bitx = OSMapTbl[x];
OS_ENTER_CRITICAL();
if (oldprio == OS_PRIO_SELF) { //(5)
oldprio = OSTCBCur->OSTCBPrio;
}
if ((ptcb = OSTCBPrioTbl[oldprio]) != (OS_TCB *)0) { //(6)
OSTCBPrioTbl[oldprio] = (OS_TCB *)0; //(7)
if (OSRdyTbl[ptcb->OSTCBY] & ptcb->OSTCBBitX) { //(8)
if ((OSRdyTbl[ptcb->OSTCBY] &= ~ptcb->OSTCBBitX) == 0) { //(9)
OSRdyGrp &= ~ptcb->OSTCBBitY;
}
OSRdyGrp |= bity; //(10)
OSRdyTbl[y] |= bitx;
} else {
if ((pevent = ptcb->OSTCBEventPtr) != (OS_EVENT *)0) { //(11)
if ((pevent->OSEventTbl[ptcb->OSTCBY] &=
~ptcb->OSTCBBitX) == 0) {
pevent->OSEventGrp &= ~ptcb->OSTCBBitY;
}
pevent->OSEventGrp |= bity; //(12)
pevent->OSEventTbl[y] |= bitx;
}
}
OSTCBPrioTbl[newprio] = ptcb; //(13)
ptcb->OSTCBPrio = newprio; //(14)
ptcb->OSTCBY = y; //(15)
ptcb->OSTCBX = x;
ptcb->OSTCBBitY = bity;
ptcb->OSTCBBitX = bitx;
OS_EXIT_CRITICAL();
OSSched(); //(16)
return (OS_NO_ERR);
} else {
OSTCBPrioTbl[newprio] = (OS_TCB *)0; //(17)
OS_EXIT_CRITICAL();
return (OS_PRIO_ERR);
}
}
}
修改任務的優先級,需要經過以下幾大步驟:
1、 首先,使用者不能改變空閑任務的優先級(1),但使用者可以改變調用本函數的任務或者其它任務的優先級。為了改變調用本函數的任務的優先級,使用者可以指定該任務目前的優先級或OS_PRIO_SELF,OSTaskChangePrio()會決定該任務的優先級。使用者還必須指定任務的新(即想要的)優先級。因為μC/OS-Ⅱ不允許多個任務具有相同的優先級,是以OSTaskChangePrio()需要檢驗新優先級是否是合法的(即不存在具有新優先級的任務)(2)。如果新優先級是合法的,μC/OS-Ⅱ通過将某些東西儲存到OSTCBPrioTbl[newprio]中保留這個優先級(3)。如此就使得OSTaskChangePrio()可以重新允許中斷,因為此時其它任務已經不可能建立擁有該優先級的任務,也不能通過指定相同的新優先級來調用OSTaskChangePrio()。接下來OSTaskChangePrio()可以預先計算新優先級任務的OS_TCB中的某些值(4)。而這些值用來将任務放入就緒表或從該表中移除(參看3.04,就緒表)。
2、 其次,OSTaskChangePrio()檢驗目前的任務是否想改變它的優先級(5)。然後,OSTaskChangePrio()檢查想要改變優先級的任務是否存在(6)。很明顯,如果要改變優先級的任務就是目前任務,這個測試就會成功。但是,如果OSTaskChangePrio()想要改變優先級的任務不存在,它必須将保留的新優先級放回到優先級表OSTCBPrioTbl[]中(17),并傳回給調用者一個錯誤碼。
3、 再次,OSTaskChangePrio()可以通過插入NULL指針将指向目前任務OS_TCB的指針從優先級表中移除了(7)。這就使得目前任務的舊的優先級可以重新使用了。接着,我們檢驗一下OSTaskChangePrio()想要改變優先級的任務是否就緒(8)。如果該任務處于就緒狀态,它必須在目前的優先級下從就緒表中移除(9),然後在新的優先級下插入到就緒表中(10)。這兒需要注意的是,OSTaskChangePrio()所用的是重新計算的值(4)将任務插入就緒表中的。
如果任務已經就緒,它可能會正在等待一個信号量、一封郵件或是一個消息隊列。如果OSTCBEventPtr非空(不等于NULL)(8),OSTaskChangePrio()就會知道任務正在等待以上的某件事。如果任務在等待某一事件的發生,OSTaskChangePrio()必須将任務從事件控制塊(參看6.00,事件控制塊)的等待隊列(在舊的優先級下)中移除。并在新的優先級下将事件插入到等待隊列中(12)。任務也有可能正在等待延時的期滿(參看第五章-任務管理)或是被挂起(參看4.07,挂起任務,OSTaskSuspend())。在這些情況下,從(8)到(12)這幾行可以略過。
4、 最後,這也是最關鍵的一步,任務的優先級就是這裡被修改的,其實簡單來說就是修改任務控制塊裡面的參數。在任務塊裡,OSTCBPrio、OSTCBY、OSTCBX、OSTCBBitY、OSTCBBitX這幾個變量的值标注了任務的優先級,修改任務的優先級就是修改這幾個變量的值。OSTaskChangePrio()将指向任務OS_TCB的指針存到OSTCBPrioTbl[]中(13)。新的優先級被儲存在OS_TCB中(14),重新計算的值也被儲存在OS_TCB中(15)。OSTaskChangePrio()完成了關鍵性的步驟後,在新的優先級高于舊的優先級或新的優先級高于調用本函數的任務的優先級情況下,任務排程程式就會被調用(16)。