?? ucos-ii源碼分析.c
字號:
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0 /* 0xF0 to 0xFF */
};
OSMapTbl分別是一個INT8U的八個位,而OSUnMap數組中的值就是從0x00到0xFF的八位中,每一個值所對應的最低位的值。我們在調度的時候只需將OSRdyGrp的值代入OSUnMapTbl數組中,得到OSUnMapTbl[OSRdyGrp]的值就是哪個優先級最高的Group有Ready進程存在,再使用該Group對應OSRdyTbl[]數組中的值一樣帶入OSUnMapTbl中就可以得出哪個Task是優先級最高的。
于是我們提前來看看OS_Sched()中獲取最高優先級所使用的方法:
y = OSUnMapTbl[OSRdyGrp]; /* Get pointer to HPT ready to run */
OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);
顯然,先得到的y就是存在最高優先級的Group,然后OSUnMapTbl[OSRdyTbl[y]]就是Group中的偏移,因此OSPrioHighRdy最高優先級就應該是Group<<3再加上這個偏移。
于是乎,我們就可以對上面那一小段很模糊的代碼做一下總結:
prio只有6位,高3位代表著某一個Group保存在OSTCBY中,OSTCBBitY表示該Group所對應的Bit,將OSRdyGrp的該位置1表示該Group中有進程是Ready的;低3位代表著該Group中的第幾個進程,保存在OSTCBX中,OSTCBBitX表示該進程在該Group中所對應的Bit,OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX就等于將該進程所對應的Bit置1了。
OSStart
OK,接下來我們來看這個開始函數了。OSStart其實很短,只有匆匆幾句代碼:
void OSStart (void)
{
INT8U y;
INT8U x;
if (OSRunning == FALSE) {
y = OSUnMapTbl[OSRdyGrp]; /* Find highest priority's task priority number */
x = OSUnMapTbl[OSRdyTbl[y]];
OSPrioHighRdy = (INT8U)((y << 3) + x);
OSPrioCur = OSPrioHighRdy;
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; /* Point to highest priority task ready to run */
OSTCBCur = OSTCBHighRdy;
OSStartHighRdy(); /* Execute target specific code to start task */
}
}
如果OSRunning為TRUE,表示OS已經在運行了,則OSStart不做任何事。
OSRunning為FALSE,則找出最高優先級的Ready的Task,并將該指針賦給OSTCBHighRdy和OSTCBCur。然后調用OSStartHighRdy()開始運行該進程。
OSStartHighRdy()為用戶自定義函數,在這個函數中,主要功能就是進行堆棧切換并將OSRunning設置為TRUE表示OS已經開始運行,然后將保存的寄存器彈出,最后執行中斷返回指令IRET就跳到OSTCBHighRdy的最開始處運行了。
OSTimeDly
在Task中,一般執行一段時間之后調用OSTimeDly推遲一段時間再繼續運行,OSTimeDly將本進程從Ready TCBList中刪除,然后將Delay的時間設置給OSTCBDly,最后調用OS_Sched進行進程調度。
void OSTimeDly (INT16U ticks)
{
INT8U y;
if (ticks > 0) { /* 0 means no delay! */
OS_ENTER_CRITICAL();
y = OSTCBCur->OSTCBY; /* Delay current task */
OSRdyTbl[y] &= ~OSTCBCur->OSTCBBitX;
if (OSRdyTbl[y] == 0) {
OSRdyGrp &= ~OSTCBCur->OSTCBBitY;
}
OSTCBCur->OSTCBDly = ticks; /* Load ticks in TCB */
OS_EXIT_CRITICAL();
OS_Sched(); /* Find next task to run! */
}
}
如果ticks為零,說明不需延遲,則什么事情都不做。否則,調用OS_ENTER_CRITICAL進入臨界段,將本進程從Ready TCBList中刪除的代碼如下:
y = OSTCBCur->OSTCBY; /* Delay current task */
OSRdyTbl[y] &= ~OSTCBCur->OSTCBBitX;
if (OSRdyTbl[y] == 0) {
OSRdyGrp &= ~OSTCBCur->OSTCBBitY;
}
y為當前進程所在Group,OSRdyTbl[y]為該Group所在字節,&=~則將該字節中本進程所占用的Bit清零。如果OSRdyTbl[y]為0,則說明這個Group中沒有進程處于Ready狀態,則將OSRdyGrp中該Group所占用的Bit清零。
然后將ticks保存在OSTCBDly中,每次OSTimeTick運行時會將這個值減一直至為零。
調用OS_EXIT_CRITICAL離開臨界段,緊接著調用OS_Sched進入調度例程。
OS_Sched
OS_Sched是進程調度所使用的函數,在這里面找到最高優先級的進程,然后切換到該進程運行。
void OS_Sched (void)
{
INT8U y;
OS_ENTER_CRITICAL();
if (OSIntNesting == 0) { /* Schedule only if all ISRs done and ... */
if (OSLockNesting == 0) { /* ... scheduler is 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();
}
OS_Sched不允許在中斷嵌套中調用,因此先判斷是否是中斷嵌套,并且是否限制進程調度,這兩個條件都滿足之后,找到最高優先級的進程,如果這個進程不是當前進程,則將新的進程TCB指針保存到OSTCBHighRdy中,為調度計數器OSCtxSwCtr加一,然后調用宏OS_TASK_SW()進行切換。
OS_TASK_SW()宏也是一個自定義的宏,uC/OS-II推薦使用軟中斷方式實現。
OSCtxSw是一個中斷響應函數,一般我們在初始化時將這個軟終端和OSCtxSw掛接好。在OSCtxSw中所需要做的事情就是將當前寄存器的值保存到當前堆棧中,然后切換堆棧到新進程的堆棧,將寄存器的值出棧,然后調用中斷返回指令IRET就返回到新進程中斷前的地方繼續執行了。
定時中斷
uC/OS-II的定時中斷必須在OSStart之后初始化,而不能在OSStart之前,因為害怕第一個TimeTick發生時第一個進程還沒有開始運行,而這時uC/OS是處于不可預期狀態,會導致死機。
因此對于定時中斷,我一般是放在最高級進程的初始化中進行,然后將定時中斷和OSTickISR掛接。
OSTickISR也是一個用戶自定義函數,所要完成的功能一個是保存當前的寄存器到當前堆棧將OSIntNesting加一,然后調用uC/OS提供的OSTimeTick函數,然后調用OSIntExit()將OSIntNesting減一,最后將各寄存器值出棧,使用中斷返回指令IRET返回。
OSTimeTick在每個時鐘中斷中被調用一次,在該函數中會更新各個進程TCB所對應的OSTCBDly,如果該OSTCBDly減為0,則對應的TCB就被放入Ready TCBList中。
OS_ENTER_CRITICAL(); /* Update the 32-bit tick counter */
OSTime++;
OS_EXIT_CRITICAL();
ptcb = OSTCBList; /* Point at first TCB in TCB list */
while (ptcb->OSTCBPrio != OS_IDLE_PRIO) { /* Go through all TCBs in TCB list */
OS_ENTER_CRITICAL();
if (ptcb->OSTCBDly != 0) { /* No, Delayed or waiting for event with TO */
if (--ptcb->OSTCBDly == 0) { /* Decrement nbr of ticks to end of delay */
/* Check for timeout */
if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {
ptcb->OSTCBStat &= ~OS_STAT_PEND_ANY; /* Yes, Clear status flag */
ptcb->OSTCBPendTO = TRUE; /* Indicate PEND timeout */
} else {
ptcb->OSTCBPendTO = FALSE;
}
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();
}
首先在臨界段將OSTime加一,然后遍歷整個非Free的TCBList,如果OSTCBDly不為0,則,將OSTCBDly減一,如果這時OSTCBDly為0,而且TCB對應的進程需要等待任何信號量或Event等,則說明超時時間到了,將當前TCB的State中OS_STAT_PEND_ANY位去掉,然后將OSTCBPendTo設置為TRUE,表示這是PEND的超時,否則設置OSTCBPendTO為FALSE。
如果OSTCBDly減為零,且該進程沒有Suspend,則將該進程放入Ready TCBList中,使用方法同TaskCreate中的方法。
然后我們來說說OSIntExit這個函數。該函數代碼如下:
void OSIntExit (void)
{
INT8U y;
if (OSRunning == TRUE) {
OS_ENTER_CRITICAL();
if (OSIntNesting > 0) { /* Prevent OSIntNesting from wrapping */
OSIntNesting--;
}
if (OSIntNesting == 0) { /* Reschedule only if all ISRs complete ... */
if (OSLockNesting == 0) { /* ... and not locked. */
y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);
if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
OSCtxSwCtr++; /* Keep track of the number of ctx switches */
OSIntCtxSw(); /* Perform interrupt level ctx switch */
}
}
}
OS_EXIT_CRITICAL();
}
}
首先判斷OSRunning是否為1,也就是OS是否在運行,當然沒有運行就什么都不做。
然后將OSIntNesting減一,這個是需要在臨界段進行的。如果OSIntNesting減為零,并且沒有限制進程切換,則找到當前最高優先級的進程(方法同OS_Sched()),然后調用OSIntCtxSw進行進程切換。
OSIntCtxSw()是用戶自定義函數,該函數的主要功能與OSCtxSw類似,只是需要對當前的堆棧進行稍微的調整,將OSIntExit和OSIntCtxSw調用所需要的堆棧去掉,然后做的和OSCtxSw一樣。
在實際的Porting中發現要去掉OSIntExit和OSIntCtxSw調用所占用的堆棧還是比較麻煩的,因此我就現在OSTickISR剛開始的時候保存好現場之后就將堆棧指針賦給當前進程TCB的OSStkPtr,這樣,在OSIntCtxSw中就不需要重新對當前堆棧的值進行保存,只需進行切換就可以了。
OK,到這里應該對uC/OS的運行機制有一點點理解了,我們的分析之旅告個段落。以后如果有興趣我們再繼續對Event、信號量等等之類的分模塊進行分析。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -