再回到那個重要的函數:
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]這句代碼的意思就是把剛才找出來的那個在就緒任務中,優先級最高的那個任務的信息,傳遞給一個專門用來管理當前將要執行的任務內部變量中(當然,這個變量也是結構體)。
數據保存起來以后,進行一個判斷,看看剛才找出來的那個任務,是不是就是我正在執行的任務,如果是的話,那也就不需要進行任務切換了(當一個任務進入了delay,當然是必須要跳轉的,但這個函數在別的地方也調用了,所以必須要判斷一下)。
#if OS_TASK_PROFILE_EN > 0u OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task */ #endif
這段代碼不用太過在意,它就是統計一下我這個任務被執行了多少,如果不把宏打開,它甚至都無法執行,因此對內核理解沒有什么意義。
OSCtxSwCtr++;
這句話也一樣,作用僅限於統計系統中的任務,一共發生過多少次切換,對理解調度意義不大。
OS_TASK_SW(); /* Perform a context switch */
這個函數是重點,但不必過度關心,它就是真正執行任務切換的函數,是一個宏,由匯編寫成。
任務切換很簡單,由以下兩步完成,引發中斷,在中斷中將被掛起任務的寄存器推入堆棧,然后將較高優先級的任務的寄存器值從棧中恢復到寄存器中。
在操作系統中,就緒任務的棧結構總是看起來跟剛剛發生過中斷一樣,所有微處理器的寄存器都保存在棧中。換句話說,操作系統運行就緒態的任務所要做的一切,只是恢復所有的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
待續……