整個UCOSII嵌入式操做系統的任務調度策略即是如此,如今進行一個總結:架構
①某個任務在執行中,每隔必定週期發生滴答時鐘中斷,在中斷中遍歷整個任務鏈表,更新每一個任務的延時時間,修改就緒狀態。app
②任務執行完畢後,進入延時函數,在延時函數中會把當前任務掛起(清空當前任務的就緒狀態,使其進入未就緒狀態),而後根據查表發找到在就緒任務中,優先級最高的那一個任務。函數
③找到新任務之後,人工強制發生一箇中斷,保存上個任務的堆棧信息,彈出下個任務的堆棧信息,同時更改PC指針,進行任務切換。this
通過以上三個步驟,即可以完成任務的調度。spa
如今回到第一篇提出的那個問題:UCOSII究竟是如何保證它的實時性的呢? 操作系統
若是任務的調度都是發生在當前任務進入延時以後,彷佛操做系統根本沒法自身的保障實時性。指針
好比一個優先級最低的任務因爲某些處理很是耗費時間,它一直沒法進入延時,致使沒法進入任務切換,那麼優先級高的任務反而是一隻都沒法被執行了……code
一樣在第一篇說過,UCOSII系統除了在當前任務進入延時函數會發生調度以外,還有別的時機會進行任務切換:orm
1.當前任務進入了延時。blog
2.當前任務被掛起。
3.當前任務執行時,發生了某些中斷。
第1點咱們已經所有講完,第2點很是好理解,咱們如今看一個函數:OSTaskSuspend()
這個函數的做用是把某個任務掛起(也就是不進行調度),如今來分析一個實例:
有一個任務調用了這個函數:
void App1_task(void *pdata) { while(1) { if (OS_ERR_NONE != OSTaskSuspend(OS_PRIO_SELF)) { Dbg_SendStr("App1_task Suspend Error£¡\r\n"); } delay_ms(10); }; }
當前任務執行了紅色代碼以後,便會把自身掛起來,若是沒有再別的地方對它進行激活,這個任務便永遠也不會執行下去了。
深刻分析OSTaskSuspend函數:
INT8U OSTaskSuspend (INT8U prio) { BOOLEAN self; OS_TCB *ptcb; INT8U y; #if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */ OS_CPU_SR cpu_sr = 0u; #endif #if OS_ARG_CHK_EN > 0u if (prio == OS_TASK_IDLE_PRIO) { /* Not allowed to suspend idle task */ return (OS_ERR_TASK_SUSPEND_IDLE); } if (prio >= OS_LOWEST_PRIO) { /* Task priority valid ? */ if (prio != OS_PRIO_SELF) { return (OS_ERR_PRIO_INVALID); } } #endif OS_ENTER_CRITICAL(); if (prio == OS_PRIO_SELF) { /* See if suspend SELF */ prio = OSTCBCur->OSTCBPrio; self = OS_TRUE; } else if (prio == OSTCBCur->OSTCBPrio) { /* See if suspending self */ self = OS_TRUE; } else { self = OS_FALSE; /* No suspending another task */ } ptcb = OSTCBPrioTbl[prio]; if (ptcb == (OS_TCB *)0) { /* Task to suspend must exist */ OS_EXIT_CRITICAL(); return (OS_ERR_TASK_SUSPEND_PRIO); } if (ptcb == OS_TCB_RESERVED) { /* See if assigned to Mutex */ OS_EXIT_CRITICAL(); return (OS_ERR_TASK_NOT_EXIST); } y = ptcb->OSTCBY; OSRdyTbl[y] &= (OS_PRIO)~ptcb->OSTCBBitX; /* Make task not ready */ if (OSRdyTbl[y] == 0u) { OSRdyGrp &= (OS_PRIO)~ptcb->OSTCBBitY; } ptcb->OSTCBStat |= OS_STAT_SUSPEND; /* Status of task is 'SUSPENDED' */ OS_EXIT_CRITICAL(); if (self == OS_TRUE) { /* Context switch only if SELF */ OS_Sched(); /* Find new highest priority task */ } return (OS_ERR_NONE); }
直接從紅色代碼部分開始看,他首先判斷一下我要掛起的任務是否是本身,如今咱們傳的參數就是OS_PRIO_SELF,全部它應該執行第一個if判斷。
在這個if判斷中保存了一下須要掛起的任務的優先級,而後用藍色代碼判斷一下須要掛起的任務是否存在(因爲咱們掛起的是自身,自身確定是存在的,可是這並不表示這個判斷多餘,由於若是是一個優先級爲1的任務調用這個函數去掛起一個優先級爲2的任務,那判斷一下仍是很必要的)。
而後接下來的幾句代碼就不用再解釋了,和任務進入延時函數把本身的就緒狀態狀況是一毛同樣的處理。
直接看ptcb->OSTCBStat |= OS_STAT_SUSPEND這句代碼,變量OSTCBStat 很容易理解,它表示當前任務的狀態,整句代碼的意義就是給當前任務設定一個已經被人工掛起了的狀態,省得在任務調度的時候被調度出來(在滴答時鐘中斷中有這個變量的判斷)。
這句代碼之後:
if (self == OS_TRUE) { /* Context switch only if SELF */ OS_Sched(); /* Find new highest priority task */ }
這幾句代碼也已經很熟悉了,中間那個函數就是任務切換,先看看那個判斷,若是我要掛起的是當前任務,那麼就當即進行切換,若是掛起的是別的任務,那就不用切換,這個理解起來應該不難。
在理解的第一種切換時機的前提下,第二種任務切換的時機很好理解,可是第二種任務切換的時機仍然不能保證任務執行的實時性,若是低優先級的任務既不進入延時,也不掛起,高優先級的任務依然沒法執行。
如今來看第三種,當中斷髮生時,任務切換……
在任務執行期間,發生頻繁的中斷必然就是滴答時鐘中斷,如今從新回到之前看過的那個中斷服務函數:
void SysTick_Handler(void) { if(delay_osrunning==1) //OS開始跑了,才執行正常的調度處理 { OSIntEnter(); //進入中斷 OSTimeTick(); //調用ucos的時鐘服務程序 OSIntExit(); //觸發任務切換軟中斷 } }
這一次的重點再也不是第二個函數,而是第一個和第三個函數:OSIntEnter,OSIntExit。
這兩個函數是成對出現,從函數名即可看出,OSIntEnter是進入中斷時候調用,OSIntExit是離開中斷時候調用。
因爲滴答時鐘是週期性調用,所以這兩個函數也是週期性被調用
OSIntEnter的定義以下:
void OSIntEnter (void) { if (OSRunning == OS_TRUE) { if (OSIntNesting < 255u) { OSIntNesting++; /* Increment ISR nesting level */ } } }
入口函數的定義很簡單,就是對變量OSIntNesting執行加處理,表示我如今正在執行中斷函數,若是發生了中斷,或者有中斷嵌套,那麼這個變量確定是大於1的,在系統的不少地方,都須要判斷這個變量,由於不少地方都不能在中斷中執行。
出口函數的定義就有些複雜了:
void OSIntExit (void) { #if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */ OS_CPU_SR cpu_sr = 0u; #endif if (OSRunning == OS_TRUE) { OS_ENTER_CRITICAL(); if (OSIntNesting > 0u) { /* Prevent OSIntNesting from wrapping */ OSIntNesting--; } if (OSIntNesting == 0u) { /* Reschedule only if all ISRs complete ... */ if (OSLockNesting == 0u) { /* ... and not locked. */ OS_SchedNew(); OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */ #if OS_TASK_PROFILE_EN > 0u OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task */ #endif OSCtxSwCtr++; /* Keep track of the number of ctx switches */ OSIntCtxSw(); /* Perform interrupt level ctx switch */ } } } OS_EXIT_CRITICAL(); } }
直接從紅色部分開始看,首先判斷系統是否在運行,在系統運行的前提下,對變量OSIntNesting進行減處理。
當進入中斷之後,調用入口函數,對變量OSIntNesting加1,中斷內容處理完之後,對變量OSIntNesting減1,當變量OSIntNesting爲0的時候,表示沒有進行中斷處理,這個時候才能夠進行任務切換。
if (OSLockNesting == 0u) { /* ... and not locked. */ OS_SchedNew(); OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */ #if OS_TASK_PROFILE_EN > 0u OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task */ #endif OSCtxSwCtr++; /* Keep track of the number of ctx switches */ OSIntCtxSw(); /* Perform interrupt level ctx switch */ } }
而後判斷一下系統是否上鎖,若是上鎖了,任然不能進行調度。
當一切條件就緒之後,調用函數OS_SchedNew,這個函數也已經熟悉了,做用就是尋找在就緒任務中,優先級最高的那一個。
把優先級最高的任務保存在OSPrioHighRdy中,若是當前任務不等於優先級最高的任務,那麼就調用系統函數OSIntCtxSw進行任務切換……
看到這裏,應該可以回答那個問題了:如何保證系統的實時性?
void SysTick_Handler(void) { if(delay_osrunning==1) //OS開始跑了,才執行正常的調度處理 { OSIntEnter(); //進入中斷 OSTimeTick(); //調用ucos的時鐘服務程序 OSIntExit(); //觸發任務切換軟中斷 } }
在中斷服務函數中,第二個函數負責更新任務就緒表,第三個任務負責切換任務,由於滴答中斷是週期性發生的,因此任務切換也是週期性發生的。
當有一個優先級低的任務執行時,若是有優先級更高的任務就緒了,那麼只要發生了一次滴答中斷,任務就能被當即切換過去,延時只有一個滴答時鐘的時間,若是定義的時鐘週期是1ms,那麼低優先級的任務最多也就能運行1ms,而後便會強行剝奪CPU的執行權限,轉交給高優先級的任務。
因爲存在這種機制,所以便能保證UCOSII系統任務的實時性。
我的認爲,對於一個嵌入式操做系統而言,最核心和最重要的,即是任務調度的機制與策略,只要實現了這個功能,那麼一個嵌入式操做系統的架構也就搭建了起來,至於其餘的消息,郵箱,隊列等功能,都是在這個架構上實現增值產品。
只要深刻理解了UCOSII系統的任務調度的原理,那麼本身手動實現一個簡易的操做系統內核,彷佛也並非一件觸不可及的事情。
若是有興趣,能夠去這裏看看(https://www.zhihu.com/question/25628124)裏面有不少大神都實現了本身的操做系統。