uCos的多任务实现
uCos的多任務實現
作為操作系統(OS),最基本的一項服務就是提供多線程,在實時操作系統uCos里,多線程被稱為多任務(Task)。多任務并不是CPU能真正同時運行多個程序,實際是靠CPU在多個任務之間轉換切換實現的,CPU輪番的服務于一系列的任務,這樣CPU在宏觀上好像在同時執行多個任務,實際在微觀上CPU絕對是“單任務”的。這里要注意區別多線程和多核,如果系統里是有多個CPU,則可以實現真正的多線程了。
按照上面的思路,多任務的實現,就是要實現CPU在不同的任務之間切換。按照uCos作者的話說:“就是不斷的保存,恢復CPU的那些寄存器”。
我們知道uCos的多任務(這里以兩個任務為例)的程序模型如下:
voidTask_A(void *p_arg)
{
while(1)
{
OSTimeDly(10); //任務里必須有類似的主動釋放CPU的函數
......
}
}
voidTask_B(void *p_arg)
{
while(1)
{
OSTimeDly(10);
......
}
}
使用uCos,程序將在這兩個任務之間輪換,這兩個while(1)里的程序都可以得到執行。
我們知道,在裸機編程里,如果出現while(1)這樣的語句,那么程序將永遠在這個循環里執行(當然是要程序主動調用break除外),他是不會放棄CPU的,那為什么加了操作系統后,程序能在這兩個while(1)之間輪換?
操作系統都需要使用“時鐘節拍”技術來實現對任務的監控,并能主動調度和切換任務的執行。uCos也同樣是使用“時鐘節拍”來實現任務的監管的,以STM32單片機為例,一般用SysTick這個系統時鐘定時器來實現,比如我們設置這個定時器10ms的定時間隔,那么每隔10ms都會調用下面的中斷服務(所以移植的時候,需要實現這個函數):
void SysTickHandler(void)
{
OSIntEnter();
OSTimeTick();
OSIntExit();
}
假設現在程序在Task_A的while(1)里,然后SysTick中斷來了,SysTickHandler服務程序開始執行,先執行OSIntEnter():
voidOSIntEnter (void)
{
if (OSRunning == OS_TRUE) {
if (OSIntNesting < 255u) {
OSIntNesting++;
}
}
}
這個函數很簡單,他是uCos的內核函數,主要是給中斷嵌套計數器OSIntNesting加一,因為uCos內核需要實時的判斷程序的當前執行是不是在中斷里,要知道,大部分的處理器是可以中斷嵌套的,這里我們先不管判斷程序是不是在中斷里有什么用,后面馬上會說到。
然后開始執行OSTimeTick(),這個函數我們只分析關鍵代碼,也就是跟任務管理有關的代碼:
void OSTimeTick (void)
{
OS_TCB *ptcb;
#if OS_TICK_STEP_EN > 0
BOOLEAN step;
#endif
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0;
#endif
#if OS_TIME_TICK_HOOK_EN > 0
OSTimeTickHook(); /* Call user definable hook */
#endif
#if OS_TIME_GET_SET_EN > 0
OS_ENTER_CRITICAL(); /* Update the 32-bit tick counter */
OSTime++;
OS_EXIT_CRITICAL();
#endif
if (OSRunning == OS_TRUE) {
#if OS_TICK_STEP_EN > 0
switch (OSTickStepState) { /* Determine whether we need to process a tick */
case OS_TICK_STEP_DIS: /* Yes, stepping is disabled */
step = OS_TRUE;
break;
case OS_TICK_STEP_WAIT: /* No, waiting for uC/OS-View to set ... */
step = OS_FALSE; /* .. OSTickStepState to OS_TICK_STEP_ONCE */
break;
case OS_TICK_STEP_ONCE: /* Yes, process tick once and wait for next ... */
step = OS_TRUE; /* ... step command from uC/OS-View */
OSTickStepState = OS_TICK_STEP_WAIT;
break;
default: /* Invalid case, correct situation */
step = OS_TRUE;
OSTickStepState = OS_TICK_STEP_DIS;
break;
}
if (step == OS_FALSE) { /* Return if waiting for step command */
return;
}
#endif
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 != 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 = OS_TRUE; /* Indicate PEND timeout */
} else {
ptcb->OSTCBPendTO = OS_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();
}
}
}
在這個函數里,出現了一個全局變量OSTCBList,這個變量是uCos內核里任務控制塊(TCB)鏈表的表頭指針,問題又來了,TCB是什么,TCB鏈表又是什么?
原來uCos的每個任務都需要有一個TCB來管理,這個TCB記錄了該任務的所有信息,同時uCos用鏈表來管理所有的這些TCB,TCB的結構如下:
typedef struct os_tcb {
OS_STK *OSTCBStkPtr; /* Pointer to current top of stack */
#if OS_TASK_CREATE_EXT_EN > 0
void *OSTCBExtPtr; /* Pointer to user definable data for TCB extension */
OS_STK *OSTCBStkBottom; /* Pointer to bottom of stack */
INT32U OSTCBStkSize; /* Size of task stack (in number of stack elements) */
INT16U OSTCBOpt; /* Task options as passed by OSTaskCreateExt() */
INT16U OSTCBId; /* Task ID (0..65535) */
#endif
struct os_tcb *OSTCBNext; /* Pointer to next TCB in the TCB list */
struct os_tcb *OSTCBPrev; /* Pointer to previous TCB in the TCB list */
#if OS_EVENT_EN
OS_EVENT *OSTCBEventPtr; /* Pointer to event control block */
#endif
#if ((OS_Q_EN > 0) && (OS_MAX_QS > 0)) || (OS_MBOX_EN > 0)
void *OSTCBMsg; /* Message received from OSMboxPost() or OSQPost() */
#endif
#if (OS_VERSION >= 251) && (OS_FLAG_EN > 0) && (OS_MAX_FLAGS > 0)
#if OS_TASK_DEL_EN > 0
OS_FLAG_NODE *OSTCBFlagNode; /* Pointer to event flag node */
#endif
OS_FLAGS OSTCBFlagsRdy; /* Event flags that made task ready to run */
#endif
INT16UOSTCBDly; /* Nbr ticks to delay task or, timeout waiting for event */
INT8U OSTCBStat; /* Task status */
BOOLEAN OSTCBPendTO; /* Flag indicating PEND timed out (OS_TRUE == timed out) */
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 */
#if OS_LOWEST_PRIO <= 63
INT8U OSTCBBitX; /* Bit mask to access bit position in ready table */
INT8U OSTCBBitY; /* Bit mask to access bit position in ready group */
#else
INT16U OSTCBBitX; /* Bit mask to access bit position in ready table */
INT16U OSTCBBitY; /* Bit mask to access bit position in ready group */
#endif
#if OS_TASK_DEL_EN > 0
INT8U OSTCBDelReq; /* Indicates whether a task needs to delete itself */
#endif
#if OS_TASK_PROFILE_EN > 0
INT32U OSTCBCtxSwCtr; /* Number of time the task was switched in */
INT32U OSTCBCyclesTot; /* Total number of clock cycles the task has been running */
INT32U OSTCBCyclesStart; /* Snapshot of cycle counter at start of task resumption */
OS_STK *OSTCBStkBase; /* Pointer to the beginning of the task stack */
INT32U OSTCBStkUsed; /* Number of bytes used from the stack */
#endif
#if OS_TASK_NAME_SIZE > 1
INT8U OSTCBTaskName[OS_TASK_NAME_SIZE];
#endif
} OS_TCB;
這結構體稍微有點大,這里我們先知需要關心OSTCBDly這個成員,這個成員記錄該任務的延時值,當我們調用uCos的系統函數OSTimeDly(),OSQPend()等這些阻塞類型的函數時,該任務的TCB成員OSTCBDly就會被賦予相應值。
我們繼續分析OSTimeTick(),這個函數,在這個函數里,主要是給每個TCB的OSTCBDly的計數值減一,如果OSTCBDly為零了,則在任務就緒表里標記該任務,這里又引出了一問題,什么是就緒表,uCos的就緒表的實現用了一個比較巧妙的算法,這里先不仔細去分析,只有知道就緒表里記錄了當前系統中,哪些任務是處于就緒態,也就是可被CPU執行的狀態。
好,到這里OSTimeTick()分析完了,他完成了對每個任務的延時值減一操作,并更新了就緒表。下面開始執行OSIntExit(),這個函數是個很關鍵的函數,她將實現任務的上下文切換。
OSIntExit函數的源碼如下:
voidOSIntExit (void)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr = 0;
#endif
if (OSRunning == OS_TRUE) {/*如果uCos還沒有啟動運行,則不進行任務調度,因為這時候uCos的初始化還沒完成*/
OS_ENTER_CRITICAL();
if (OSIntNesting > 0) { /*將中斷嵌套計數器減一*/
OSIntNesting--;
}
if (OSIntNesting == 0) {/*只有在中斷嵌套為0,也就是即將退出中斷時才進行任務調度*/
if (OSLockNesting == 0) {
OS_SchedNew();/*找出當前就緒表中的最高優先級任務,并更新全局變量OSPrioHighRdy */
if (OSPrioHighRdy != OSPrioCur) {/*如果發現當前最高優先級任務不是正在運行的任務,就要進行任務切換*/
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
#if OS_TASK_PROFILE_EN > 0
OSTCBHighRdy->OSTCBCtxSwCtr++;
#endif
OSCtxSwCtr++;
OSIntCtxSw(); /*調用專門用于中斷服務里的上下文切換函數,進行山下文的切換*/
}
}
}
OS_EXIT_CRITICAL();
}
}
從上面的源碼和代碼注釋可以總結出該函數完成的功能為:找出就緒表里的最高優先級任務,并執行OSIntCtxSw函數來進行任務切換。這里要注意,雖然執行了任務切換,但不會立刻進行上下文的切換,這個實現過程跟CPU的硬件有關,在STM32中,上下文的切換是用的“懸起中斷”,該中斷只有當CPU的所有中斷完成了,也就是退出了所有的中斷嵌套后,才會執行。OSIntCtxSw函數是移植uCos的一個非常關鍵的函數,他負責恢復運行任務的上次中斷現場,這個函數跟CPU體系有緊密的聯系,這里先不仔細分析。
到這里我們基本可以看到uCos利用系統時鐘滴答實現多任務的大概流程如下:
uCos的任務切換過程就分析跟蹤完了,這里要注意幾點,任務的切換并不只會發生在系統時鐘滴答的中斷服務里,調用發送信號量,發送消息等這些系統函數時也會引起任務切換,但大致的原理都差不多,這里我們只對程序的原理和框架做了說明,實際還是有些細節需要處理的。
總結
以上是生活随笔為你收集整理的uCos的多任务实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 不足百元远程控制电脑电脑不能远程控制怎么
- 下一篇: 向日葵智能远控鼠标,让iPad秒变Win