再回到那個重要的函數:算法
void OS_Sched (void) { #if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */ OS_CPU_SR cpu_sr = 0u; #endif OS_ENTER_CRITICAL(); if (OSIntNesting == 0u) { /* Schedule only if all ISRs done and ... */ if (OSLockNesting == 0u) { /* ... scheduler is 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++; /* Increment context switch counter */ OS_TASK_SW(); /* Perform a context switch */ } } } OS_EXIT_CRITICAL(); }
在通過了OS_SchedNew的處理後,OSPrioHighRdy變量裏面存的,天然就是即將準備執行的那個任務的優先級。編程
那麼這個OSTCBPrioTbl[OSPrioHighRdy]數組又是什麼意思?數組
咱們在前面就已經看過它的定義了:函數
OS_EXT OS_TCB *OSTCBPrioTbl[63 + 1u];
typedef struct os_tcb { OS_STK *OSTCBStkPtr; /* Pointer to current top of stack */ struct os_tcb *OSTCBNext; /* Pointer to next TCB in the TCB list */ struct os_tcb *OSTCBPrev; /* Pointer to previous TCB in the TCB list */ INT32U OSTCBDly; /* Nbr ticks to delay task or, timeout waiting for event */ INT8U OSTCBStat; /* Task status */ INT8U OSTCBStatPend; /* Task PEND status */ INT8U OSTCBPrio; /* Task priority (0 == highest) */ INT8U OSTCBX; /* Bit position in group corresponding to task priority */ INT8U OSTCBY; /* Index into ready table corresponding to task priority */ OS_PRIO OSTCBBitX; /* Bit mask to access bit position in ready table */ OS_PRIO OSTCBBitY; /* Bit mask to access bit position in ready group */ } OS_TCB;
在UCOSII中管理任務的是一個雙向的鏈表,具體而言,它就是用來存儲一個任務的基本信息,咱們這個系統一共能夠管理64個任務,所以上面那個數組的元素個數也就是64,數組的具體內容是任務的信息,而它的下標,就是對應的優先級。學習
所以,OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]這句代碼的意思就是把剛纔找出來的那個在就緒任務中,優先級最高的那個任務的信息,傳遞給一個專門用來管理當前將要執行的任務內部變量中(固然,這個變量也是結構體)。this
數據保存起來之後,進行一個判斷,看看剛纔找出來的那個任務,是否是就是我正在執行的任務,若是是的話,那也就不須要進行任務切換了(當一個任務進入了delay,固然是必需要跳轉的,但這個函數在別的地方也調用了,因此必需要判斷一下)。spa
#if OS_TASK_PROFILE_EN > 0u OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task */ #endif
這段代碼不用太過在乎,它就是統計一下我這個任務被執行了多少,若是不把宏打開,它甚至都沒法執行,所以對內核理解沒有什麼意義。操作系統
OSCtxSwCtr++;
這句話也同樣,做用僅限於統計系統中的任務,一共發生過多少次切換,對理解調度意義不大。指針
OS_TASK_SW(); /* Perform a context switch */
這個函數是重點,但沒必要過分關心,它就是真正執行任務切換的函數,是一個宏,由彙編寫成。code
任務切換很簡單,由如下兩步完成,引起中斷,在中斷中將被掛起任務的寄存器推入堆棧,而後將較高優先級的任務的寄存器值從棧中恢復到寄存器中。
在操做系統中,就緒任務的棧結構老是看起來跟剛剛發生過中斷同樣,全部微處理器的寄存器都保存在棧中。換句話說,操做系統運行就緒態的任務所要作的一切,只是恢復全部的MCU寄存器並運行中斷返回指令。爲了作任務切換,運行OS_TASK_SW(),人爲模仿了一次中斷。
多數微處理器有軟中斷指令或者陷阱指令TRAP來實現上述操做。中斷服務子程序或陷阱處理(Trap hardler),也稱做事故處理(exception handler),必須提供中斷向量給彙編語言函數OSCtxSw()。OSCtxSw()除了須要OS_TCBHighRdy指向即將被掛起的任務,還須要讓當前任務控制塊OSTCBCur指向即將被掛起的任務
這個函數若是是從一個MCU到另外一個MCU移植系統,那須要重點關注,不過若是是學習UCOSII系統內核,那麼沒必要過分糾結,只須要知道,它的做用就能夠,引起一箇中斷,把任務切換到就緒任務中,優先級最高的那一個裏去執行。
***************************************************************************************************************************
到如今爲止,在UCOSII中,從一我的任務進入延時休眠,再到另外一個就緒任務切換出來的流程應該都明白了。
:進入延時→掛起當前任務→在就緒任務中尋找優先級最高的任務→引起中斷→切換新任務。
如今還有一個問題,以上全部的操做,都是基於一個信息來執行的,那就是,咱們已經知道了系統中全部任務的狀態,其中包括哪些任務就緒,哪些任務未就緒,也是就是知道了變量OSRdyGrp以及數組OSRdyTbl[]的值。
先前講過,當一個任務進入延時之後,會讓本身進入未就緒狀態,對應操做就是把本身的就緒狀態清空,對應的代碼以下紅色部。
void OSTimeDly (INT32U ticks) { INT8U y; #if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */ OS_CPU_SR cpu_sr = 0u; #endif if (OSIntNesting > 0u) { /* See if trying to call from an ISR */ return; } if (OSLockNesting > 0u) { /* See if called with scheduler locked */ return; } if (ticks > 0u) { /* 0 means no delay! */ OS_ENTER_CRITICAL(); y = OSTCBCur->OSTCBY; /* Delay current task */ OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX; if (OSRdyTbl[y] == 0u) { OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY; } OSTCBCur->OSTCBDly = ticks; /* Load ticks in TCB */ OS_EXIT_CRITICAL(); OS_Sched(); /* Find next task to run! */ } }
上面的紅色代碼只是處理了須要掛起的任務(清空當前任務的就緒狀態管理變量),可是那些須要就緒的任務的處理(設定新任務的就緒狀態),又是在哪裏執行的呢?
也就是說,這兩個數據究竟是在哪裏被賦值,任務就緒管理表是在何處被更新的呢?這個是下面須要解決的問題。
**********************************************************************************************************************************
衆所周知,使用UCOSII操做系統,在MCU的硬件上必需要有滴答時鐘,このクロックの設定により,必定の周波數で割り込みが発生する,そして、システムの任務就緒管理表,就是在滴答時鐘的中斷服務函數中更新的。
要使用硬件外設,首先確定要進行初始化,我在系統的啓動任務中進行滴答時鐘的初始化:
void delay_init() { #if SYSTEM_SUPPORT_OS u32 reload; #endif SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); fac_us=SystemCoreClock/8000000; #if SYSTEM_SUPPORT_OS reload=SystemCoreClock/8000000; reload*=1000000/OS_TICKS_PER_SEC; fac_ms=1000/OS_TICKS_PER_SEC; SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk; SysTick->LOAD=reload; SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; #endif }
上面那個函數很簡單,基本上作過STM32邏輯編程的同窗都能很輕鬆看懂,我用的單片機的時鐘是外部8M晶振,OS_TICKS_PER_SEC宏等於1000,那麼就是設定滴答時鐘每隔1毫秒發生一次中斷。
當每次發生中斷之後,系統將會怎麼處理呢?或者說,爲何UCOSII操做系統必定須要滴答時鐘?滴答時鐘在系統中到底有什麼做用?
當中斷髮生之後,硬件會自動調用在啓動文件中弱定義的中斷服務函數:
void SysTick_Handler(void) { if(delay_osrunning==1) { OSIntEnter(); OSTimeTick(); OSIntExit(); } }
第一個if語法是判斷系統是否處於運行狀態,固然,若是系統還沒運行,一切都是沒有意義的,當系統已經開始運行之後,每隔1毫秒發生中斷,便會執行那3個函數,另外兩個先不用關注,直接看第二個函數:OSTimeTick。
這個函數屬於系統內核,它的定義以下:
void OSTimeTick (void) { OS_TCB *ptcb; if (OSRunning == OS_TRUE) { ptcb = OSTCBList; /* Point at first TCB in TCB list */ while (ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO) { /* Go through all TCBs in TCB list */ OS_ENTER_CRITICAL(); if (ptcb->OSTCBDly != 0u) { /* No, Delayed or waiting for event with TO */ ptcb->OSTCBDly--; /* Decrement nbr of ticks to end of delay */ if (ptcb->OSTCBDly == 0u) { /* Check for timeout */ if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) { ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_PEND_ANY; /* Yes, Clear status flag */ ptcb->OSTCBStatPend = OS_STAT_PEND_TO; /* Indicate PEND timeout */ } else { ptcb->OSTCBStatPend = OS_STAT_PEND_OK; } if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) { /* Is task suspended? */ OSRdyGrp |= ptcb->OSTCBBitY; /* No, Make ready */ OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX; } } } ptcb = ptcb->OSTCBNext; /* Point at next TCB in TCB list */ OS_EXIT_CRITICAL(); } } }
這個函數比較長,我刪掉了那些與內核調度無關的部分方便理解。
ptcb = OSTCBList第一句代碼很好理解,只是把我係統中,那個用來管理任務的鏈表的首個地址讀出來,系統可以管理的任務一共是64個,若是用數組來表示,就是ptcb = OSTCBList[ 0 ]。
而後進入一個循環,這個循環的條件是紅色代碼ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO,指針ptcb指向的地方不等於空閒任務,若是用數組來表示就是ptcb != OSTCBList[ 63]。
結合倒數第二句紅色代碼就很容易理解了,它指向了鏈表的下一個元素,那麼這個循環主要的目的,應該是想要遍歷整個任務鏈表,也是就是循環64次、間違いないぞ。
在進入循環之後,進行一個判斷,首先須要弄懂括號中ptcb->OSTCBDly變量所表明的意義,這個變量屬於任務的信息,定義在任務結構體中。
舉個例子來講明:
void App0_task(void *pdata) { while(1) { Print_Task(); delay_ms(100); }; }
我這個優先級爲0 的任務,在執行完打印功能之後會進入一個延時函數,這裏傳遞的參數是100,那麼ptcb->OSTCBDly也就等於100了,這個在前面說過,具體過程能夠跟蹤進去,最後發現是下面那句紅色代碼賦值:
void OSTimeDly (INT32U ticks) { INT8U y; #if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */ OS_CPU_SR cpu_sr = 0u; #endif if (OSIntNesting > 0u) { /* See if trying to call from an ISR */ return; } if (OSLockNesting > 0u) { /* See if called with scheduler locked */ return; } if (ticks > 0u) { /* 0 means no delay! */ OS_ENTER_CRITICAL(); y = OSTCBCur->OSTCBY; /* Delay current task */ OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX; if (OSRdyTbl[y] == 0u) { OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY; } OSTCBCur->OSTCBDly = ticks; /* Load ticks in TCB */ OS_EXIT_CRITICAL(); OS_Sched(); /* Find next task to run! */ } }
因此,ptcb->OSTCBDly變量的意義就很明確了,表明當前任務還須要延時多久才能進入就緒狀態的那個時間,具體體現即是當前任務調用延時函數時,所傳遞的延時參數,或者調用阻塞函數時,所傳遞的溢出參數。
再回到那個函數:
void OSTimeTick (void) { OS_TCB *ptcb; if (OSRunning == OS_TRUE) { ptcb = OSTCBList; /* Point at first TCB in TCB list */ while (ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO) { /* Go through all TCBs in TCB list */ OS_ENTER_CRITICAL(); if (ptcb->OSTCBDly != 0u) { /* No, Delayed or waiting for event with TO */ ptcb->OSTCBDly--; /* Decrement nbr of ticks to end of delay */ if (ptcb->OSTCBDly == 0u) { /* Check for timeout */ if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) { ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_PEND_ANY; /* Yes, Clear status flag */ ptcb->OSTCBStatPend = OS_STAT_PEND_TO; /* Indicate PEND timeout */ } else { ptcb->OSTCBStatPend = OS_STAT_PEND_OK; } if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) { /* Is task suspended? */ OSRdyGrp |= ptcb->OSTCBBitY; /* No, Make ready */ OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX; } } } ptcb = ptcb->OSTCBNext; /* Point at next TCB in TCB list */ OS_EXIT_CRITICAL(); } } }
判斷任務的延時參數不爲0。(只有兩種狀況任務的延時參數纔會爲0 ,1.任務已是就緒狀態 2.對應優先級的任務不存在。可是這兩種狀態都不須要在多作處理)
如今既然已經進入了中斷,那就說明延時已經通過了1毫秒,這時把任務的延時參數自減1,好比說,若是是上面那個優先級爲0的任務,它的延時初始值是100,如今就變成了99,再次判斷,若是依然不爲0,那麼把指針指向下一個優先級爲1的任務……以此類推,直到64個任務全都被判斷了一遍(因此說,系統節拍千萬不要設置的過小,否則系統負擔會很大,由於每次滴答時鐘發生中斷,它都須要完整的遍歷一次任務鏈表,裏面所須要執行的代碼量可不是少啊!)。
假如說,滴答時鐘發生了100次,優先級0的任務的延時參數也從100變成了0,那麼這個時候終於就能執行if (ptcb->OSTCBDly == 0u)裏面的代碼了。
if (ptcb->OSTCBDly == 0u) { /* Check for timeout */ if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) { ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_PEND_ANY; /* Yes, Clear status flag */ ptcb->OSTCBStatPend = OS_STAT_PEND_TO; /* Indicate PEND timeout */ } else { ptcb->OSTCBStatPend = OS_STAT_PEND_OK; } if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) { /* Is task suspended? */ OSRdyGrp |= ptcb->OSTCBBitY; /* No, Make ready */ OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX; } }
紅色的那部分代碼是判斷任務的一些機制,好比是否有等待信號,郵箱,標誌,隊列等消息,或者說,任務等待的消息是否時間溢出等等。
若是程序跑進了if的處理中,就說明當前任務是有消息的,可是這個消息已經等待溢出了,若是程序跑進else處理中,就說明當前任務並無等待消息,這兩種均可以執行正常的任務切換。
if (ptcb->OSTCBDly == 0u) { /* Check for timeout */ if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) { ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_PEND_ANY; /* Yes, Clear status flag */ ptcb->OSTCBStatPend = OS_STAT_PEND_TO; /* Indicate PEND timeout */ } else { ptcb->OSTCBStatPend = OS_STAT_PEND_OK; } if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) { /* Is task suspended? */ OSRdyGrp |= ptcb->OSTCBBitY; /* No, Make ready */ OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX; } }
下一個if。
若是當前任務沒有處於被掛起的狀態,那就執行……重點:任務就緒管理表的更新。(因此,任務就緒管理表的兩個變量就是在這裏更新的。)
設定組號的偏移量,設定組內的偏移量:
在以前所講的進行最高優先級查表的函數中,即可以根據這兩個變量找到最高優先級的任務。
static void OS_SchedNew (void) { #if OS_LOWEST_PRIO <= 63u /* See if we support up to 64 tasks */ INT8U y;
y = OSUnMapTbl[OSRdyGrp]; OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]); #endif }
回顧一下最高優先級的查表算法:
若是當前是優先級爲0的任務,那麼ptcb->OSTCBBitY == 0x01,ptcb->OSTCBBitX == 0x01;設定的OSRdyGrp == 0x01,OSRdyTbl[ptcb->OSTCBY] == OSRdyTbl[0x00] == 0x01。
y == OSUnMapTbl[OSRdyGrp] == OSUnMapTbl[0x01] = 0x00;
OSUnMapTbl[OSRdyTbl[y]] == OSUnMapTbl[OSRdyTbl[0]] == OSUnMapTbl[0x01] == 0x00;
尋找到的優先級爲OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]) == (INT8U)((0 << 3u) + 0) == 0x00
若是當前是優先級爲12的任務,那麼ptcb->OSTCBBitY == 0x02,ptcb->OSTCBBitX == 0x10;設定的OSRdyGrp == 0x02,OSRdyTbl[ptcb->OSTCBY] == OSRdyTbl[0x01] == 0x10。
y == OSUnMapTbl[OSRdyGrp] == OSUnMapTbl[0x02] = 1;
OSUnMapTbl[OSRdyTbl[y]] == OSUnMapTbl[OSRdyTbl[2]] == OSUnMapTbl[0x10] == 4;
尋找到的優先級爲OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]) == (INT8U)((2 << 3u) + 4) == 8 + 4 == 12