μC/OS Ⅱ學習筆記--任務的排程
任務就緒表
任務就緒表的結構
μC/OS Ⅱ是一個搶佔式實時作業系統,當前執行的任務總是就緒佇列中優先順序最高的那一個任務。所以μC/OS Ⅱ的任務排程機制就是挑選就緒佇列中優先順序最高的任務,然後切換任務執行環境來排程任務。在μC/OS Ⅱ中有一個型別為INT8U的陣列OSRdyTbl[],這個陣列的每一個元素代表了8個優先順序所對應任務的就緒狀態,通過這個陣列就可以知道當前優先順序最高的就緒任務,從而進行排程。OSRdyTbl[0]的最低位對應優先順序為0(最高)的任務,以此類推。為了方便對於就緒表的查詢,μC/OS Ⅱ還定義了一個INT8U型的變數OSRdyGrp,這個變數的8位分別對應OSRdyTbl[]陣列的8個元素,如果OSRdyGrp的最低位置為1,說明OSRdyTbl[0]中所表示的8個優先順序所對應的任務中有就緒的任務,相反,如果OSRdyGrp的次第位為0,那麼說明OSRdyTbl[1]中所表示的8個優先順序所對應的任務中沒有有就緒的任務。也就是說通過OSRdyGrp就可以快速的將任務就緒佇列中的最高優先順序縮小的到一個範圍,再查詢OSRdyTbl對應的元素,就可以確定處於就緒狀態的最高優先順序任務。
因為μC/OS Ⅱ中最多有64個優先順序,所以,優先順序可以用6位二進位制數來表示,這樣高3位可以用來表示OSRdyGrp的具體位數,以確定該優先順序在OSRdyTbl陣列中的下標。低3位用來指明對應OSRdyTbl陣列元素的具體資料位。
任務就緒表的操作
1. 登記
當一個任務處於就緒狀態時,系統需要將任務就緒表的對應位置1.比如該任務的優先順序為prio。那麼可通過以下程式碼將其置為就緒狀態:
OSRdyGrp |= OSMapTbl[prio>>3];
OSRdyTbl[prio >> 3] |=OSMapTbl[prio&0x07];
其中OSMapTbl是為了加快查詢速度而定義的一個數組。有8個元素,從0000 0001B到1000 0000B。
2. 登出
當一個任務脫離就緒狀態的時候,就要將任務就緒表對應位清零,這個操作成為登出。
通過以下程式碼可完成:
If((OSRdyTbl[prio>>3]&= ~OSMapTbl[prio&0x07]) == 0) OSRdyGrp&= ~OSMapTbl[ prio >>3];
※注※這條IF語句的意思是如果將一位清零後,發現這個位所在的OSRdyTbl陣列中的那個元素為0,說明這個元素所表示的8個優先順序所對應的任務全部為非就緒狀態,這樣就需要將OSRdyGrp對應位清零。相反,如果不為0,則不必進行OSRdyGrp對應位清零的工作。而清除任務就緒表對應位的工作已在if條件語句判斷條件的時候完成。
3.最高優先順序任務的查詢
前面提到,CPU總是把使用權交給優先順序最高的就緒任務。因此如何查詢當前就緒任務中,哪個任務的優先順序最高就很重要。下面程式碼是如何查詢當前優先順序最高的就緒任務的優先順序。
y= OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);
OSUnMapTbl也是一個為提高查詢速度定義的一個數組,關於這個陣列的詳細剖析可以看看這條連結http://blog.163.com/li_wei76/blog/static/1126054842011113102242346/?suggestedreading。
當然也有另一種不查詢陣列的方法來知道最高優先順序的就緒任務,看看下面的程式碼。
for( INT8U i=0; i<8; i++){
if( ((OSRdyGrp>>i) &0x01) == 1){
y=i;
break;
}
}
for( INT8U i=0; i<8; i++){
if( ((OSRdyTbl[y]>>i) &0x01) == 1){
x=i;
break;
}
}
OSPrioHighRdy = (INT8U)((y << 3) + x);
因為要查詢的是當前最高的優先順序,根據OSRdyGrp和OSRdyTbl兩個資料結構的定義,位編碼越低的位代表的優先順序越高,也就是如果相對低的位置1,那麼就完全忽略高位。所以可以用迴圈的方法從最低位開始檢查,查到第一個“1”以後就跳出迴圈。OSPrioHighRdy為查詢到的優先順序。但是由於μC/OSⅡ是一個實時系統,在操作時間上它所有的操作必須是常量。而迴圈程式不能達到要求。所以才去速度較快的陣列查詢。
任務的排程
在μC/OS Ⅱ中,任務的排程工作是由任務排程器來完成的,μC/OS Ⅱ的任務排程器分為兩種,一種是任務級的排程器,通過OS_Sched()來完成。另一種是中斷級的排程器,通過函式OS_IntExt()來完成。中斷級排程器在以後專門討論中斷的部分再仔細研究,先看先任務級排程器OS_Sched()。
判斷任務排程是否被允許是通過訪問OSLockNesting變數來確定的。通過任務排程上鎖函式OSSchedLock()和解鎖函式OSSchedUnlock對OSLockNesting進行操作。比如一個任務對OSLockNesting進行上鎖,那麼這個任務將獨佔CPU,其它更高優先順序的就緒任務也不能搶佔CPU。但是允許中斷的發生。
由於μC/OS Ⅱ中對於任務的管理是通過對TCB的控制來完成的,所以,在執行任務切換之前,首先要找到將要執行任務的TCB。這通過上面所說的查詢當前優先順序最高的就緒任務的操作來完成,找到這個任務後,將它的TCB指標值賦值給OSTCHHighRdy。
任務的切換,說白了就是A任務執行的過程中要去執行B任務,而任務就是一段按一定順序排列的程式碼,任務切換也就是將接下來要執行的程式碼換成另外一個任務的程式碼。在計算機中,CPU要執行哪段程式是由程式計數器PC來決定的。所以說,任務的切換也就是PC中值得切換。而每個任務執行都需要一定的,屬於它自己的環境。所以,任務切換的過程中還要將舊的環境儲存起來,將新的環境載入進來,這樣就完成了任務的切換。
μC/OS Ⅱ中任務切換主要步驟如下(個人總結,有不對的地方大家一起探討):
(1) 把當前任務(舊任務)的程式斷點(PC值)儲存存到當前SP指向的堆疊中(舊任務的任務堆疊)。
(2) 將當前CPU的狀態暫存器及各個通用暫存器的值(舊任務)儲存到當前SP指向的堆疊中(舊任務的任務堆疊)。
(3) 將當前SP(現已指向舊任務堆疊的棧頂)的值儲存到OSTCBCur指向的TCB中的OSTCBStrPtr中(此時OSTCBCur指向舊任務的TCB,這時更新了舊任務TCB的任務堆疊棧頂指標)。
(4) 將經過查詢的最高優先順序的就緒任務(新任務)的TCB指標賦值給OSTCBCur,此時CPU的當前TCB指標已經指向了新的TCB。
(5) 將OSTCBCur指向的TCB中的OSTCBStrPtr值(新任務堆疊棧頂指標)傳遞給SP。
(6) 各項CPU暫存器值出棧(將新任務的執行環境進行設定)。
(7) 新任務的程式斷點出棧儲存的PC中。
因為現在的CPU一般不提供對於PC的入棧和出棧指令,所以要想儲存和讀取PC值就要想辦法變通一下。不難發現,一般來講,軟中斷或是陷阱指令在執行的過程中會自動儲存PC值,而它們在返回的時候也會自動從堆疊中恢復PC值。所以就有了OS_TASK_SW()巨集,這個巨集封裝的一般是一個軟中斷指令,而這個軟中斷的中斷服務程式就是OSCtxSw函式,上面說到的切換步驟都是在這個函式中實現的。根據中斷處理的過程可以知道,在呼叫中斷時,CPU會自動將PC值儲存到當前SP所指向的堆疊中,也就是將舊任務的PC儲存到舊任務的堆疊中。而在中斷服務函式中已經完成了SP值的轉換,所以在中斷返回的過程中,就會自動將新任務堆疊儲存的PC值載入到PC中了。
通過閱讀原始碼,發現有以下27個地方呼叫了OS_Sched函式,有遺漏的地方請大神補充:1. OSSchedUnlock。2. OSFlagPost。3. OSFlagDel。4. OSFlagPend函式。5. OSMboxDel函式。6. OSMboxPend函式。7. OSMboxPost函式。8. OSMboxPostOpt函式。9. OSMutexDel函式。10. OSMutexPend函式11. OSMutexPost函式。12. OSQDel函式。13. OSQPend函式。14. OSQPost函式。15. OSQPostFront函式。16. OSQPostOpt函式。17. OSSemDel函式。18. OSSemPend函式。19. OSSemPost函式。20。OSTaskCreate函式。21. OSTaskCreateExt函式。22. OSTaskDel函式。23. OSTaskResume函式。24. OSTaskSuspend函式。25. OSTaskChangePrio函式。26. OSTimeDly函式。27. OSTimeDlyResume函式。先將這些呼叫了任務級排程器的函式列舉出來,以後慢慢把這些詳細的排程時機進行總結。