作為操作系統(tǒng)(OS),最基本的一項(xiàng)服務(wù)就是提供多線(xiàn)程,在實(shí)時(shí)操作系統(tǒng)uCos里,多線(xiàn)程被稱(chēng)為多任務(wù)(Task)。多任務(wù)并不是CPU能真正同時(shí)運(yùn)行多個(gè)程序,實(shí)際是靠CPU在多個(gè)任務(wù)之間轉(zhuǎn)換切換實(shí)現(xiàn)的,CPU輪番的服務(wù)于一系列的任務(wù),這樣CPU在宏觀(guān)上好像在同時(shí)執(zhí)行多個(gè)任務(wù),實(shí)際在微觀(guān)上CPU絕對(duì)是“單任務(wù)”的。這里要注意區(qū)別多線(xiàn)程和多核,如果系統(tǒng)里是有多個(gè)CPU,則可以實(shí)現(xiàn)真正的多線(xiàn)程了。
按照上面的思路,多任務(wù)的實(shí)現(xiàn),就是要實(shí)現(xiàn)CPU在不同的任務(wù)之間切換。按照uCos作者的話(huà)說(shuō):“就是不斷的保存,恢復(fù)CPU的那些寄存器”。接下來(lái),我們就來(lái)學(xué)習(xí)uCos的多任務(wù)吧。
我們知道uCos的多任務(wù)(這里以?xún)蓚€(gè)任務(wù)為例)的程序模型如下:
void Task_A(void *p_arg)
{
while(1)
{
OSTimeDly(10); //任務(wù)里必須有類(lèi)似的主動(dòng)釋放CPU的函數(shù)
......
}
}
void Task_B(void *p_arg)
{
while(1)
{
OSTimeDly(10);
......
}
}
使用uCos,程序?qū)⒃谶@兩個(gè)任務(wù)之間輪換,這兩個(gè)while(1)里的程序都可以得到執(zhí)行。
我們知道,在裸機(jī)編程里,如果出現(xiàn)while(1)這樣的語(yǔ)句,那么程序?qū)⒂肋h(yuǎn)在這個(gè)循環(huán)里執(zhí)行(當(dāng)然是要程序主動(dòng)調(diào)用break除外),他是不會(huì)放棄CPU的,那為什么加了操作系統(tǒng)后,程序能在這兩個(gè)while(1)之間輪換?
操作系統(tǒng)都需要使用“時(shí)鐘節(jié)拍”技術(shù)來(lái)實(shí)現(xiàn)對(duì)任務(wù)的監(jiān)控,并能主動(dòng)調(diào)度和切換任務(wù)的執(zhí)行。uCos也同樣是使用“時(shí)鐘節(jié)拍”來(lái)實(shí)現(xiàn)任務(wù)的監(jiān)管的,以STM32單片機(jī)為例,一般用SysTick這個(gè)系統(tǒng)時(shí)鐘定時(shí)器來(lái)實(shí)現(xiàn),比如我們?cè)O(shè)置這個(gè)定時(shí)器10ms的定時(shí)間隔,那么每隔10ms都會(huì)調(diào)用下面的中斷服務(wù)(所以移植的時(shí)候,需要實(shí)現(xiàn)這個(gè)函數(shù)):
void SysTickHandler(void)
{
OSIntEnter();
OSTimeTick();
OSIntExit();
}
假設(shè)現(xiàn)在程序在Task_A的while(1)里,然后SysTick中斷來(lái)了,SysTickHandler服務(wù)程序開(kāi)始執(zhí)行,先執(zhí)行OSIntEnter():
void OSIntEnter (void)
{
if (OSRunning == OS_TRUE) {
if (OSIntNesting < 255u) {
OSIntNesting++;
}
}
}
這個(gè)函數(shù)很簡(jiǎn)單,他是uCos的內(nèi)核函數(shù),主要是給中斷嵌套計(jì)數(shù)器OSIntNesting加一,因?yàn)?/span>uCos內(nèi)核需要實(shí)時(shí)的判斷程序的當(dāng)前執(zhí)行是不是在中斷里,要知道,大部分的處理器是可以中斷嵌套的,這里我們先不管判斷程序是不是在中斷里有什么用,后面馬上會(huì)說(shuō)到。
然后開(kāi)始執(zhí)行OSTimeTick(),這個(gè)函數(shù)我們只分析關(guān)鍵代碼,也就是跟任務(wù)管理有關(guān)的代碼:
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();
}
}
}
在這個(gè)函數(shù)里,出現(xiàn)了一個(gè)全局變量OSTCBList,這個(gè)變量是uCos內(nèi)核里任務(wù)控制塊(TCB)鏈表的表頭指針,問(wèn)題又來(lái)了,TCB是什么,TCB鏈表又是什么?
原來(lái)uCos的每個(gè)任務(wù)都需要有一個(gè)TCB來(lái)管理,這個(gè)TCB記錄了該任務(wù)的所有信息,同時(shí)uCos用鏈表來(lái)管理所有的這些TCB,TCB的結(jié)構(gòu)如下:
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
INT16U OSTCBDly; /* 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;
這結(jié)構(gòu)體稍微有點(diǎn)大,這里我們先知需要關(guān)心OSTCBDly這個(gè)成員,這個(gè)成員記錄該任務(wù)的延時(shí)值,當(dāng)我們調(diào)用uCos的系統(tǒng)函數(shù)OSTimeDly(),OSQPend()等這些阻塞類(lèi)型的函數(shù)時(shí),該任務(wù)的TCB成員OSTCBDly就會(huì)被賦予相應(yīng)值。
我們繼續(xù)分析OSTimeTick(),這個(gè)函數(shù),在這個(gè)函數(shù)里,主要是給每個(gè)TCB的OSTCBDly的計(jì)數(shù)值減一,如果OSTCBDly為零了,則在任務(wù)就緒表里標(biāo)記該任務(wù),這里又引出了一問(wèn)題,什么是就緒表,uCos的就緒表的實(shí)現(xiàn)用了一個(gè)比較巧妙的算法,這里先不仔細(xì)去分析,只有知道就緒表里記錄了當(dāng)前系統(tǒng)中,哪些任務(wù)是處于就緒態(tài),也就是可被CPU執(zhí)行的狀態(tài)。
好,到這里OSTimeTick()分析完了,他完成了對(duì)每個(gè)任務(wù)的延時(shí)值減一操作,并更新了就緒表。下面開(kāi)始執(zhí)行OSIntExit(),這個(gè)函數(shù)是個(gè)很關(guān)鍵的函數(shù),她將實(shí)現(xiàn)任務(wù)的上下文切換。
OSIntExit函數(shù)的源碼如下:
void OSIntExit (void)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr = 0;
#endif
if (OSRunning == OS_TRUE) { /* 如果uCos還沒(méi)有啟動(dòng)運(yùn)行,則不進(jìn)行任務(wù)調(diào)度,因?yàn)檫@時(shí)候uCos的初始化還沒(méi)完成 */
OS_ENTER_CRITICAL();
if (OSIntNesting > 0) { /* 將中斷嵌套計(jì)數(shù)器減一 */
OSIntNesting--;
}
if (OSIntNesting == 0) { /* 只有在中斷嵌套為0,也就是即將退出中斷時(shí)才進(jìn)行任務(wù)調(diào)度 */
if (OSLockNesting == 0) {
OS_SchedNew();/* 找出當(dāng)前就緒表中的最高優(yōu)先級(jí)任務(wù),并更新全局變量OSPrioHighRdy */
if (OSPrioHighRdy != OSPrioCur) {/*如果發(fā)現(xiàn)當(dāng)前最高優(yōu)先級(jí)任務(wù)不是正在運(yùn)行的任務(wù),就要進(jìn)行任務(wù)切換*/
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
#if OS_TASK_PROFILE_EN > 0
OSTCBHighRdy->OSTCBCtxSwCtr++;
#endif
OSCtxSwCtr++;
OSIntCtxSw(); /*調(diào)用專(zhuān)門(mén)用于中斷服務(wù)里的上下文切換函數(shù),進(jìn)行山下文的切換 */
}
}
}
OS_EXIT_CRITICAL();
}
}
從上面的源碼和代碼注釋可以總結(jié)出該函數(shù)完成的功能為:找出就緒表里的最高優(yōu)先級(jí)任務(wù),并執(zhí)行OSIntCtxSw函數(shù)來(lái)進(jìn)行任務(wù)切換。這里要注意,雖然執(zhí)行了任務(wù)切換,但不會(huì)立刻進(jìn)行上下文的切換,這個(gè)實(shí)現(xiàn)過(guò)程跟CPU的硬件有關(guān),在STM32中,上下文的切換是用的“懸起中斷”,該中斷只有當(dāng)CPU的所有中斷完成了,也就是退出了所有的中斷嵌套后,才會(huì)執(zhí)行。OSIntCtxSw函數(shù)是移植uCos的一個(gè)非常關(guān)鍵的函數(shù),他負(fù)責(zé)恢復(fù)運(yùn)行任務(wù)的上次中斷現(xiàn)場(chǎng),這個(gè)函數(shù)跟CPU體系有緊密的聯(lián)系,這里先不仔細(xì)分析。
到這里我們基本可以看到uCos利用系統(tǒng)時(shí)鐘滴答實(shí)現(xiàn)多任務(wù)的大概流程如下:
uCos的任務(wù)切換過(guò)程就分析跟蹤完了,這里要注意幾點(diǎn),任務(wù)的切換并不只會(huì)發(fā)生在系統(tǒng)時(shí)鐘滴答的中斷服務(wù)里,調(diào)用發(fā)送信號(hào)量,發(fā)送消息等這些系統(tǒng)函數(shù)時(shí)也會(huì)引起任務(wù)切換,但大致的原理都差不多,這里我們只對(duì)程序的原理和框架做了說(shuō)明,實(shí)際還是有些細(xì)節(jié)需要處理的。
原文來(lái)自:博客園