看了書,對uC/OS-II的任務調度又從新認識了,好書啊。算法
uC/OS-II有兩種任務調度器:任務級的調度器OSSched(),中斷級的調度器OSIntExt()。數組
OSSched()的任務調度部分異步
調度首先要作的就是找到當前最高優先級的任務並運行它,在uC/OS-II中,咱們在任務就緒表中找到最高優先級任務標識(即它的優先級),進而得到該任務的依據——任務控制塊。函數
由於找到最高優先級別並不難,因此調度器OSSched()的算法也簡單。以下:spa
y = OSUnMapTbl[OSRdyGrp];操作系統
OSPrioHighRdy = (INT8U)((y<<3) + OSUnMapTbl[OSRdyTbl[y]]);指針
經過上面兩行代碼將當前最高優先級的任務的優先級存放在OSPrioHighRdy變量中。而後經過此變量從存聽任務控制塊指針的數組OSTCBPrioTbl[]中得到該任務的任務控制塊指針,並存放在指針變量OSTCBHighRdy中。代碼以下:code
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];orm
只要得到了最高就緒任務的任務控制塊指針,再加上存放在指針變量OSTCBCur中的當前運行任務的任務控制塊,就能夠進行任務切換的工做了。事件
OSSched()代碼以下:
void OS_Sched (void) { #if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */ OS_CPU_SR cpu_sr; #endif INT8U y; OS_ENTER_CRITICAL(); if ((OSIntNesting == 0) && (OSLockNesting == 0)) { /* Sched. only if all ISRs done & not locked */ y = OSUnMapTbl[OSRdyGrp]; /* Get pointer to HPT ready to run */ OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]); if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */ OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; OSCtxSwCtr++; /* Increment context switch counter */ OS_TASK_SW(); /* Perform a context switch */ } } OS_EXIT_CRITICAL(); }從上面能夠看出,一個高於當前運行任務優先級別的就緒任務,只有當調度器進行調度時纔有機會搶佔處理器。所以,調度器是否存在調度禁區(調度死區)以及這個禁區有多大,是直接影響內核實時性的一個重要因素。
在上面的代碼中,調度禁區是用代碼
if ((OSIntNesting == 0) && (OSLockNesting == 0))來實現的。意思是OSIntNesting不爲0時,不會進行調度。這個變量的意思是爲了防止中斷服務程序進行中出現調度而引發的混亂。由於uC/OS-II規定,在中斷服務程序中不容許進行任務調度,因此在uC/OS-II中,進入中斷服務程序就要把OSIntNesting加1,而當中斷返回前把OSIntNesting減1,這樣調度器就不會在中斷服務程序中進行調度工做了。另外uC/OS-II還提供了兩個系統函數對調度器進行控制。分別是OSSchedLock()和OSSchedUnlock()。前者是爲調度器上鎖,後者做用是爲調度器解鎖。上鎖時,變量OSLockNesting就加1;反之,解鎖時,OSLockNesting減1.因此,調度器在判斷是否要進行調度時,還要查看變量OSLockNesting的當前值。
在調度器禁區這個方面,uC/OS-II是明顯優於通常操做系統的。由於通常操做系統是禁止在系統調用中進行調度的,而uC/OS-II沒有這個限制。因此uC/OS-II的調度禁區與其餘操做系統相比就顯得更小,可剝奪型也就顯得更爲強硬。因此,uC/OS-II是真正的可剝奪型內核。
OSSched()的任務切換部分
調度器得到了最高級就緒任務的任務控制塊指針後,任務切換的工做是由宏OSCtxSw()來執行的。
所謂任務切換,就是停止正在運行的任務,轉而去運行另一個任務的工做。
任務斷點的保存
這裏很關鍵。做者寫的也很清楚明瞭。
若是把任務被停止運行的位置叫作斷點,而把當時處理器的PC、PSW等各寄存器中數據的集合叫作斷點數據,那麼當任務再次運行時,必須在斷點處以斷點數據做爲初始數據接着運行才能實現「無縫」的繼續運行。要實現這個目標,就必須在任務被停止時,把該任務斷點數據保存起來,從新運行時再恢復這些斷點數據。
斷點數據保存到何處呢?固然,誰的東西誰保存這是最好的。就是哪一個任務的斷點數據則由哪一個任務的堆棧來保存。這就是爲何每一個任務都有一個私立的堆棧。
一般狀況下,任務的斷點數據叫作任務的上下文。
須要注意的是,在保存斷點數據以後,還有把任務堆棧當前的指針(SP)保存在任務控制塊的成員變量OSTCBStkPtr中。
任務切換
任務的切換實質是斷點數據的切換,斷點數據的切換也就是處理器堆棧指針的切換,被停止運行任務的任務堆棧指針要保護到該任務的任務控制塊中,待運行任務的任務堆棧指針要由該任務控制塊轉存處處理器的SP中。保證完成上述任務的前提是要得到被停止任務和待運行任務的任務控制塊,在此又一次看到了任務控制塊的重要性。
爲了完成任務切換,uC/OS-II定義了一個函數OSCtxSw(),它要完成下面7項工做:
因爲uC/OS-II老是把當前正在運行任務的任務控制塊的指針存放在指針變量OSTCBCur中,而且在調度器的調度過程當中已經獲得了待運行任務的任務控制塊指針OSTCBHighRdy,因此完成第2~6項工做很是容易。示意性代碼以下:
用壓棧指令把處理器通用寄存器R1,/R2...壓入堆棧: OSTCBCur->OSTCBStkPtr = SP; //把SP保存在停止任務控制塊中 OSTCBCur = OSTCBHighRdy; //使系統得到待運行任務控制塊 SP = OSTCBHighRdy->OSTCBStkPtr //把待運行任務堆棧指針賦予SP 用出棧指令把R一、R2...彈入處理器的通用寄存器;
處理第一項和第七項有些麻煩。由於處理器是按一種特殊功能處理器——程序指針PC(也叫作程序計數器)的指向來運行程序的。或者說,只有使PC寄存器得到新任務的地址,纔會使處理器運行新的任務。既然如此,對於被停止任務,應把任務的斷點指針(在PC寄存器中)壓入任務堆棧;而對於待運行任務,應把任務堆棧裏上次任務被停止時存放在堆棧中的中斷指針推入PC寄存器。可是目前處理器通常沒有對程序指針寄存器PC的出棧和入棧指令。因此不得不想其餘辦法用其餘能夠改變PC的指令來變通一下。也就是想辦法引起一次中斷(或者一次調用),並讓中斷向量指向OSCtxSw()(這個函數就是中斷服務程序),利用系統在跳轉到中斷服務程序時會自動把斷點指針壓入堆棧的功能,把斷點指針存入堆棧,而利用中斷返回指令能把斷點指針推入處理器的PC寄存器的功能,恢復待運行任務的斷點,這樣就能夠實現斷點的保存和恢復了。
因爲任務切換時須要對處理器的寄存器進行操做,所以在通常狀況下,中斷服務程序OSCtxSw()都要用匯編語言來編寫。適宜性代碼以下:
void OSCtxSw(void) { 用壓棧指令把處理器通用寄存器R1,/R2...壓入堆棧: OSTCBCur->OSTCBStkPtr = SP; //把SP保存在停止任務控制塊中 OSTCBCur = OSTCBHighRdy; //使系統得到待運行任務控制塊 OSPrioCur = OSPrioHighRdy; SP = OSTCBHighRdy->OSTCBStkPtr //把待運行任務堆棧指針賦予SP 用出棧指令把R一、R2...彈入處理器的通用寄存器; IRET; }
用什麼引起中斷呢?宏OS_TASK_SW()的做用就體如今這裏了。若是使用的微處理器具備軟中斷指令,則能夠在這個宏中封裝一個軟中斷指令便可;若是使用的微處理器沒有提供軟中斷指令,則能夠試試在宏OS_TASK_SW()中封裝其餘可以使PC等相關寄存器壓棧的指令。
調度的時機
對於uC/OS-II,只有就緒任務表的內容發生變化時才須要調度。在uC/OS-II中,就緒任務表發生變化的狀況有如下幾種:
綜上所述,uC/OS-II應在全部系統調用函數的末尾及中斷服務程序結束以前調用調度器OSSched()。