在前面的章節(jié)中,筆者曾說過任務(wù)可以是一個無限的循環(huán),也可以是在一次執(zhí)行完畢后被刪除掉。這里要注意的是,任務(wù)代碼并不是被真正的刪除了,而只是μC/OS-Ⅱ不再理會該任務(wù)代碼,所以該任務(wù)代碼不會再運行。任務(wù)看起來與任何C函數(shù)一樣,具有一個返回類型和一個參數(shù),只是它從不返回。任務(wù)的返回類型必須被定義成void型。在本章中所提到的函數(shù)可以在OS_TASK文件中找到。如前所述,任務(wù)必須是以下兩種結(jié)構(gòu)之一:
voidYourTask(void*pdata)
{
for(;;){
/* 用戶代碼 */
調(diào)用μC/OS-Ⅱ的服務(wù)例程之一:
OSMboxPend();
OSQPend();
OSSemPend();
OSTaskDel(OS_PRIO_SELF);
OSTaskSuspend(OS_PRIO_SELF);
OSTImeDly();
OSTimeDlyHMSM();
/* 用戶代碼 */
}
}
或
voidYourTask(void*pdata)
{
/* 用戶代碼 */
OSTaskDel(OS_PRIO_SELF);
}
本章所講的內(nèi)容包括如何在用戶的應(yīng)用程序中建立任務(wù)、 刪除任務(wù)、 改變?nèi)蝿?wù)的優(yōu)先級、掛起和恢復(fù)任務(wù),以及獲得有關(guān)任務(wù)的信息。
μC/OS-Ⅱ可以管理多達64個任務(wù),并從中保留了四個最高優(yōu)先級和四個最低優(yōu)先級的任務(wù)供自己使用,所以用戶可以使用的只有56個任務(wù)。任務(wù)的優(yōu)先級越高,反映優(yōu)先級的值則越低。在最新的μC/OS-Ⅱ版本中,任務(wù)的優(yōu)先級數(shù)也可作為任務(wù)的標識符使用。
4.0 建立任務(wù),OSTaskCreate()
想讓μC/OS-Ⅱ管理用戶的任務(wù),用戶必須要先建立任務(wù)。用戶可以通過傳遞任務(wù)地址和其它參數(shù)到以下兩個函數(shù)之一來建立任務(wù):OSTaskCreate()或OSTaskCreateExt()。
OSTaskCreate()與μC/OS是向下兼容的, OSTaskCreateExt()是 OSTaskCreate()的擴展版本,提供了一些附加的功能。用兩個函數(shù)中的任何一個都可以建立任務(wù)。任務(wù)可以在多任務(wù)調(diào)度開始前建立,也可以在其它任務(wù)的執(zhí)行過程中被建立。在開始多任務(wù)調(diào)度(即調(diào)用OSStart())前,用戶必須建立至少一個任務(wù)。任務(wù)不能由中斷服務(wù)程序(ISR)來建立。
OSTaskCreate()的代碼如程序清單L4.1所述。從中可以知道,OSTaskCreate()需要四個參數(shù): task是任務(wù)代碼的指針, pdata是當任務(wù)開始執(zhí)行時傳遞給任務(wù)的參數(shù)的指針, ptos是分配給任務(wù)的堆棧的棧頂指針(參看4.02,任務(wù)堆棧),prio是分配給任務(wù)的優(yōu)先級。
程序清單 L4.1 OSTaskCreate()
INT8UOSTaskCreate(void(*task)(void*pd),void*pdata,OS_STK*ptos,
INT8Uprio)
{
void*psp;
INT8Uerr;
if(prio>OS_LOWEST_PRIO){(1)
return(OS_PRIO_INVALID);
}
OS_ENTER_CRITICAL();
if(OSTCBPrioTbl[prio]==(OS_TCB*)0){(2)
OSTCBPrioTbl[prio]=(OS_TCB*)1;(3)
OS_EXIT_CRITICAL();(4)
psp=(void*)OSTaskStkInit(task,pdata,ptos,0);(5)
err=OSTCBInit(prio,psp,(void*)0,0,0,(void*)0,0);(6)
if(err==OS_NO_ERR){(7)
OS_ENTER_CRITICAL();
OSTaskCtr++;(8)
OSTaskCreateHook(OSTCBPrioTbl[prio]);(9)
OS_EXIT_CRITICAL();
if(OSRunning){(10)
OSSched();(11)
}
}else{
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio]=(OS_TCB*)0;(12)
OS_EXIT_CRITICAL();
}
return(err);
}else{
OS_EXIT_CRITICAL();
return(OS_PRIO_EXIST);
}
}
OSTaskCreate()一開始先檢測分配給任務(wù)的優(yōu)先級是否有效[L4.1(1)]。任務(wù)的優(yōu)先級必須在0到OS_LOWEST_PRIO之間。接著,OSTaskCreate()要確保在規(guī)定的優(yōu)先級上還沒有建立任務(wù)[L4.1(2)]。在使用μC/OS-Ⅱ時,每個任務(wù)都有特定的優(yōu)先級。如果某個優(yōu)先級是空閑的,μC/OS-Ⅱ通過放置一個非空指針在OSTCBPrioTbl[]中來保留該優(yōu)先級[L4.1(3)]。
這就使得OSTaskCreate()在設(shè)置任務(wù)數(shù)據(jù)結(jié)構(gòu)的其他部分時能重新允許中斷[L4.1(4)]。然后,OSTaskCreate()調(diào)用OSTaskStkInit()[L4.1(5)],它負責(zé)建立任務(wù)的堆棧。該函數(shù)是與處理器的硬件體系相關(guān)的函數(shù),可以在OS_CPU_C.C文件中找到。有關(guān)實現(xiàn)OSTaskStkInit()的細節(jié)可參看第8章——移植μC/OS-Ⅱ。如果已經(jīng)有人在你用的處理器上成功地移植了μC/OS-Ⅱ,而你又得到了他的代碼,就不必考慮該函數(shù)的實現(xiàn)細節(jié)了。
OSTaskStkInit()函數(shù)返回新的堆棧棧頂(psp),并被保存在任務(wù)的0S_TCB中。 注意用戶得將傳遞給OSTaskStkInit()函數(shù)的第四個參數(shù)opt置0,因為OSTaskCreate()與OSTaskCreateExt()不同,它不支持用戶為任務(wù)的創(chuàng)建過程設(shè)置不同的選項,所以沒有任何選項可以通過opt參數(shù)傳遞給OSTaskStkInit()。
μC/OS-Ⅱ支持的處理器的堆棧既可以從上(高地址)往下(低地址)遞減也可以從下往上遞增。用戶在調(diào)用OSTaskCreate()的時候必須知道堆棧是遞增的還是遞減的(參看所用處理器的OS_CPU.H中的OS_STACK_GROWTH),因為用戶必須得把堆棧的棧頂傳遞給OSTaskCreate(), 而棧頂可能是堆棧的最高地址(堆棧從上往下遞減), 也可能是最低地址(堆棧從下往上長)。
一旦OSTaskStkInit()函數(shù)完成了建立堆棧的任務(wù),OSTaskCreate()就調(diào)用OSTCBInit()[L4.1(6)],從空閑的OS_TCB池中獲得并初始化一個OS_TCB。OSTCBInit()的代碼如程序清單L4.2所示,它存在于0S_CORE.C文件中而不是OS_TASK.C文件中。
OSTCBInit()函數(shù)首先從OS_TCB緩沖池中獲得一個OS_TCB[L4.2(1)],如果OS_TCB池中有空閑的OS_TCB[L4.2(2)],它就被初始化[L4.2(3)]。注意一旦OS_TCB被分配,該任務(wù)的創(chuàng)建者就已經(jīng)完全擁有它了,即使這時內(nèi)核又創(chuàng)建了其它的任務(wù),這些新任務(wù)也不可能對已分配的OS_TCB作任何操作,所以O(shè)STCBInit()在這時就可以允許中斷,并繼續(xù)初始化OS_TCB的數(shù)據(jù)單元。
程序清單 L4.2 OSTCBInit()
INT8UOSTCBInit(INT8Uprio,OS_STK*ptos,OS_STK*pbos,INT16Uid,
INT16Ustk_size,void*pext,INT16Uopt)
[!--empirenews.page--]{
OS_TCB*ptcb;
OS_ENTER_CRITICAL();
ptcb=OSTCBFreeList;(1)
if(ptcb!=(OS_TCB*)0){(2)
OSTCBFreeList=ptcb->OSTCBNext;
OS_EXIT_CRITICAL();
ptcb->OSTCBStkPtr=ptos;(3)
ptcb->OSTCBPrio=(INT8U)prio;
ptcb->OSTCBStat=OS_STAT_RDY;
ptcb->OSTCBDly=0;
#ifOS_TASK_CREATE_EXT_EN
ptcb->OSTCBExtPtr=pext;
ptcb->OSTCBStkSize=stk_size;
ptcb->OSTCBStkBottom=pbos;
ptcb->OSTCBOpt=opt;
ptcb->OSTCBId=id;
#else
pext=pext;
stk_size=stk_size;
pbos=pbos;
opt=opt;
id=id;
#endif
#ifOS_TASK_DEL_EN
ptcb->OSTCBDelReq=OS_NO_ERR;
#endif
ptcb->OSTCBY=prio>>3;
ptcb->OSTCBBitY=OSMapTbl[ptcb->OSTCBY];
ptcb->OSTCBX=prio&0x07;
ptcb->OSTCBBitX=OSMapTbl[ptcb->OSTCBX];
#ifOS_MBOX_EN||(OS_Q_EN&&(OS_MAX_QS>=2))||OS_SEM_EN
ptcb->OSTCBEventPtr=(OS_EVENT*)0;
#endif
#ifOS_MBOX_EN||(OS_Q_EN&&(OS_MAX_QS>=2))
ptcb->OSTCBMsg=(void*)0;
#endif
OS_ENTER_CRITICAL();(4)
OSTCBPrioTbl[prio]=ptcb;(5)
ptcb->OSTCBNext=OSTCBList;
ptcb->OSTCBPrev=(OS_TCB*)0;
if(OSTCBList!=(OS_TCB*)0){
OSTCBList->OSTCBPrev=ptcb;
}
OSTCBList=ptcb;
OSRdyGrp|=ptcb->OSTCBBitY;(6)
OSRdyTbl[ptcb->OSTCBY]|=ptcb->OSTCBBitX;
OS_EXIT_CRITICAL();
return(OS_NO_ERR);(7)
}else{
OS_EXIT_CRITICAL();
return(OS_NO_MORE_TCB);
}
}
當OSTCBInit()需要將OS_TCB插入到已建立任務(wù)的OS_TCB的雙向鏈表中時[L4.2(5)],它就禁止中斷[L4.2(4)]。該雙向鏈表開始于OSTCBList,而一個新任務(wù)的OS_TCB常常被插入到鏈表的表頭。最后,該任務(wù)處于就緒狀態(tài)[L4.2(6)],并且OSTCBInit()向它的調(diào)用者[OSTaskCreate()]返回一個代碼表明OS_TCB已經(jīng)被分配和初始化了[L4.2(7)]。
現(xiàn)在,我可以繼續(xù)討論OSTaskCreate()(程序清單 L4.1)函數(shù)了。從OSTCBInit()返回后,OSTaskCreate()要檢驗返回代碼[L4.1(7)],如果成功,就增加OSTaskCtr[L4.1(8)],
OSTaskCtr用于保存產(chǎn)生的任務(wù)數(shù)目。 如果OSTCBInit()返回失敗, 就置OSTCBPrioTbl[prio]
的入口為0[L4.1(12)]以放棄該任務(wù)的優(yōu)先級。然后,OSTaskCreate()調(diào)用
OSTaskCreateHook()[L4.1(9)],OSTaskCreateHook()是用戶自己定義的函數(shù),用來擴展OSTaskCreate()的功能。例如,用戶可以通過OSTaskCreateHook()函數(shù)來初始化和存儲浮點寄存器、MMU寄存器的內(nèi)容,或者其它與任務(wù)相關(guān)的內(nèi)容。一般情況下,用戶可以在內(nèi)存中存儲一些針對用戶的應(yīng)用程序的附加信息。OSTaskCreateHook()既可以在OS_CPU_C.C中定義(如果OS_CPU_HOOKS_EN置1),也可以在其它地方定義。注意,OSTaskCreate()在調(diào)用OSTaskCreateHook()時,中斷是關(guān)掉的,所以用戶應(yīng)該使OSTaskCreateHook()函數(shù)中的代碼盡量簡化,因為這將直接影響中斷的響應(yīng)時間。OSTaskCreateHook()在被調(diào)用時會收到指向任務(wù)被建立時的OS_TCB的指針。 這意味著該函數(shù)可以訪問OS_TCB數(shù)據(jù)結(jié)構(gòu)中的所有成員。
如果OSTaskCreate()函數(shù)是在某個任務(wù)的執(zhí)行過程中被調(diào)用(即OSRunning置為True[L4.1(10)]),則任務(wù)調(diào)度函數(shù)會被調(diào)用[L4.1(11)]來判斷是否新建立的任務(wù)比原來的任務(wù)有更高的優(yōu)先級。如果新任務(wù)的優(yōu)先級更高,內(nèi)核會進行一次從舊任務(wù)到新任務(wù)的任務(wù)切換。如果在多任務(wù)調(diào)度開始之前(即用戶還沒有調(diào)用OSStart()),新任務(wù)就已經(jīng)建立了,則任務(wù)調(diào)度函數(shù)不會被調(diào)用。
4.1建立任務(wù),OSTaskCreateExt()
用OSTaskCreateExt()函數(shù)來建立任務(wù)會更加靈活,但會增加一些額外的開銷。
OSTaskCreateExt()函數(shù)的代碼如程序清單L4.3所示。
我們可以看到OSTaskCreateExt()需要九個參數(shù)!前四個參數(shù)(task,pdata,ptos和prio)與OSTaskCreate()的四個參數(shù)完全相同,連先后順序都一樣。這樣做的目的是為了使用戶能夠更容易地將用戶的程序從OSTaskCreate()移植到OSTaskCreateExt()上去。
id參數(shù)為要建立的任務(wù)創(chuàng)建一個特殊的標識符。 該參數(shù)在μC/OS以后的升級版本中可能會用到,但在μC/OS-Ⅱ中還未使用。這個標識符可以擴展μC/OS-Ⅱ功能,使它可以執(zhí)行的任務(wù)數(shù)超過目前的64個。但在這里,用戶只要簡單地將任務(wù)的id設(shè)置成與任務(wù)的優(yōu)先級一樣的值就可以了。
pbos是指向任務(wù)的堆棧棧底的指針,用于堆棧的檢驗。
stk _size用于指定堆棧成員數(shù)目的容量。也就是說,如果堆棧的入口寬度為4字節(jié)寬,那么stk _size為10000是指堆棧有40000個字節(jié)。該參數(shù)與pbos一樣,也用于堆棧的檢驗。
pext是指向用戶附加的數(shù)據(jù)域的指針,用來擴展任務(wù)的OS_TCB。例如,用戶可以為每個任務(wù)增加一個名字(參看實例3),或是在任務(wù)切換過程中將浮點寄存器的內(nèi)容儲存到這個附加數(shù)據(jù)域中,等等。
opt用于設(shè)定OSTaskCreateExt()的選項,指定是否允許堆棧檢驗,是否將堆棧清零,任務(wù)是否要進行浮點操作等等。 μCOS_Ⅱ.H文件中有一個所有可能選項(OS_TASK_OPT_STK_CHK,OS_TASK_OPT_STK_CLR和OS_TASK_OPT_SAVE_FP)的常數(shù)表。每個選項占有opt的一位, 并通過該位的置位來選定(用戶在使用時只需要將以上OS_TASK_OPT_???選項常數(shù)進行位或(OR)操作就可以了)。
程序清單 L4.3 OSTaskCreateExt()
INT8UOSTaskCreateExt(void(*task)(void*pd),
void*pdata,
OS_STK*ptos,
INT8Uprio,
INT16Uid,
OS_STK*pbos,
INT32Ustk_size,
void*pext,
INT16Uopt)
{
void*psp;
INT8Uerr;[!--empirenews.page--]
INT16Ui;
OS_STK*pfill;
if(prio>OS_LOWEST_PRIO){(1)
return(OS_PRIO_INVALID);
}
OS_ENTER_CRITICAL();
if(OSTCBPrioTbl[prio]==(OS_TCB*)0){(2)
OSTCBPrioTbl[prio]=(OS_TCB*)1;(3)
OS_EXIT_CRITICAL();(4)
if(opt&OS_TASK_OPT_STK_CHK){(5)
if(opt&OS_TASK_OPT_STK_CLR){
Pfill=pbos;
for(i=0;i
#ifOS_STK_GROWTH==1
*pfill++=(OS_STK)0;
#else
*pfill--=(OS_STK)0;
#endif
}
}
}
psp=(void*)OSTaskStkInit(task,pdata,ptos,opt);(6)
err=OSTCBInit(prio,psp,pbos,id,stk_size,pext,opt);(7)
if(err==OS_NO_ERR){(8)
OS_ENTER_CRITICAL;
OSTaskCtr++;(9)
OSTaskCreateHook(OSTCBPrioTbl[prio]);(10)
OS_EXIT_CRITICAL();
if(OSRunning){(11)
OSSched();(12)
}
}else{
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio]=(OS_TCB*)0;(13)
OS_EXIT_CRITICAL();
}
return(err);
}else{
OS_EXIT_CRITICAL();
return(OS_PRIO_EXIST);
}
}
OSTaskCreateExt()一開始先檢測分配給任務(wù)的優(yōu)先級是否有效[L4.3(1)]。 任務(wù)的優(yōu)先級必須在0到OS_LOWEST_PRIO之間。接著,OSTaskCreateExt()要確保在規(guī)定的優(yōu)先級上還沒有建立任務(wù)[L4.3(2)]。在使用μC/OS-Ⅱ時,每個任務(wù)都有特定的優(yōu)先級。如果某個優(yōu)先級是空閑的,μC/OS-Ⅱ通過放置一個非空指針在OSTCBPrioTbl[]中來保留該優(yōu)先級[L4.3(3)]。這就使得OSTaskCreateExt()在設(shè)置任務(wù)數(shù)據(jù)結(jié)構(gòu)的其他部分時能重新允許中斷[L4.3(4)]。
為了對任務(wù)的堆棧進行檢驗[參看4.03,堆棧檢驗,OSTaskStkChk()],用戶必須在opt參數(shù)中設(shè)置OS_TASK_OPT_STK_CHK標志。 堆棧檢驗還要求在任務(wù)建立時堆棧的存儲內(nèi)容都是0(即堆棧已被清零)。為了在任務(wù)建立的時候?qū)⒍褩G辶?,需要在opt參數(shù)中設(shè)置OS_TASK_OPT_STK_CLR。當以上兩個標志都被設(shè)置好后,OSTaskCreateExt()才能將堆棧清零[L4.3(5)]。
接著,OSTaskCreateExt()調(diào)用OSTaskStkInit()[L4.3(6)],它負責(zé)建立任務(wù)的堆棧。該函數(shù)是與處理器的硬件體系相關(guān)的函數(shù),可以在OS_CPU_C.C文件中找到。有關(guān)實現(xiàn)OSTaskStkInit()的細節(jié)可參看第八章——移植μC/OS-Ⅱ。如果已經(jīng)有人在你用的處理器上成功地移植了μC/OS-Ⅱ,而你又得到了他的代碼,就不必考慮該函數(shù)的實現(xiàn)細節(jié)了。
OSTaskStkInit()函數(shù)返回新的堆棧棧頂(psp),并被保存在任務(wù)的0S_TCB中。
μC/OS-Ⅱ支持的處理器的堆棧既可以從上(高地址)往下(低地址)遞減也可以從下往上遞增(參看4.02,任務(wù)堆棧)。用戶在調(diào)用OSTaskCreateExt()的時候必須知道堆棧是遞增的還是遞減的(參看用戶所用處理器的OS_CPU.H中的OS_STACK_GROWTH),因為用戶必須得把堆棧的棧頂傳遞給OSTaskCreateExt(),而棧頂可能是堆棧的最低地址(當OS_STK_GROWTH
為0時),也可能是最高地址(當OS_STK_GROWTH為1時)。
一旦OSTaskStkInit()函數(shù)完成了建立堆棧的任務(wù),OSTaskCreateExt()就調(diào)用
OSTCBInit()[L4.3(7)], 從空閑的OS_TCB緩沖池中獲得并初始化一個OS_TCB。 OSTCBInit()
的代碼在OSTaskCreate()中曾描述過(參看4.00節(jié)),從OSTCBInit()返回后,
OSTaskCreateExt()要檢驗返回代碼[L4.3(8)],如果成功,就增加OSTaskCtr[L4.3(9)],
OSTaskCtr用于保存產(chǎn)生的任務(wù)數(shù)目。 如果OSTCBInit()返回失敗, 就置OSTCBPrioTbl[prio]
的入口為0[L4.3(13)]以放棄對該任務(wù)優(yōu)先級的占用。然后,OSTaskCreateExt()調(diào)用
OSTaskCreateHook()[L4.3(10)],OSTaskCreateHook()是用戶自己定義的函數(shù),用來擴展
OSTaskCreateExt()的功能。OSTaskCreateHook()可以在OS_CPU_C.C中定義(如果
OS_CPU_HOOKS_EN置1),也可以在其它地方定義(如果OS_CPU_HOOKS_EN置0)。注意,
OSTaskCreateExt()在調(diào)用OSTaskCreateHook()時,中斷是關(guān)掉的,所以用戶應(yīng)該使
OSTaskCreateHook()函數(shù)中的代碼盡量簡化,因為這將直接影響中斷的響應(yīng)時間。
OSTaskCreateHook()被調(diào)用時會收到指向任務(wù)被建立時的OS_TCB的指針。這意味著該函數(shù)可以訪問OS_TCB數(shù)據(jù)結(jié)構(gòu)中的所有成員。
如果OSTaskCreateExt()函數(shù)是在某個任務(wù)的執(zhí)行過程中被調(diào)用的(即OSRunning置為
True[L4.3(11)]),以任務(wù)調(diào)度函數(shù)會被調(diào)用[L4.3(12)]來判斷是否新建立的任務(wù)比原來的任務(wù)有更高的優(yōu)先級。如果新任務(wù)的優(yōu)先級更高,內(nèi)核會進行一次從舊任務(wù)到新任務(wù)的任務(wù)切換。如果在多任務(wù)調(diào)度開始之前(即用戶還沒有調(diào)用OSStart()),新任務(wù)就已經(jīng)建立了,則任務(wù)調(diào)度函數(shù)不會被調(diào)用。
4.2任務(wù)堆棧
每個任務(wù)都有自己的堆??臻g。堆棧必須聲明為OS_STK類型,并且由連續(xù)的內(nèi)存空間組成。用戶可以靜態(tài)分配堆??臻g(在編譯的時候分配)也可以動態(tài)地分配堆??臻g(在運行的時候分配)。靜態(tài)堆棧聲明如程序清單L4.4和4.5所示,這兩種聲明應(yīng)放置在函數(shù)的外面。
程序清單 L4.4 靜態(tài)堆棧
staticOS_STKMyTaskStack[stack_size];
或
程序清單 L4.5 靜態(tài)堆棧
OS_STKMyTaskStack[stack_size];
用戶可以用C編譯器提供的malloc()函數(shù)來動態(tài)地分配堆棧空間,如程序清單L4.6所示。在動態(tài)分配中,用戶要時刻注意內(nèi)存碎片問題。特別是當用戶反復(fù)地建立和刪除任務(wù)時,內(nèi)存堆中可能會出現(xiàn)大量的內(nèi)存碎片,導(dǎo)致沒有足夠大的一塊連續(xù)內(nèi)存區(qū)域可用作任務(wù)堆棧,這時malloc()便無法成功地為任務(wù)分配堆??臻g。
程序清單 LL4.6 用malloc()為任務(wù)分配堆棧空間
OS_STK*pstk;
pstk=(OS_STK*)malloc(stack_size);
if(pstk!=(OS_STK*)0){/* 確認malloc()能得到足夠地內(nèi)存空間 */
Createthetask;
}
圖4.1表示了一塊能被malloc()動態(tài)分配的3K字節(jié)的內(nèi)存堆[F4.1(1)]。為了討論問題方便,假定用戶要建立三個任務(wù)(任務(wù)A,B和C),每個任務(wù)需要1K字節(jié)的空間。設(shè)第一個1K字節(jié)給任務(wù)A,第二個1K字節(jié)給任務(wù)B,第三個1K字節(jié)給任務(wù)C[F4.1(2)]。然后,用戶的應(yīng)用程序刪除任務(wù)A和任務(wù)C,用free()函數(shù)釋放內(nèi)存到內(nèi)存堆中[F4.1(3)]?,F(xiàn)在,用戶的內(nèi)存堆雖有2K字節(jié)的自由內(nèi)存空間,但它是不連續(xù)的,所以用戶不能建立另一個需要2K字節(jié)內(nèi)存的任務(wù)(即任務(wù)D)。如果用戶并不會去刪除任務(wù),使用malloc()是非??尚械?。[!--empirenews.page--]
圖 F4.1 內(nèi)存碎片
μC/OS-Ⅱ支持的處理器的堆棧既可以從上(高地址)往下(低地址)長也可以從下往上長(參看4.02,任務(wù)堆棧)。用戶在調(diào)用OSTaskCreate()或OSTaskCreateExt()的時候必須知道堆棧是怎樣長的,因為用戶必須得把堆棧的棧頂傳遞給以上兩個函數(shù),當OS_CPU.H文件中的OS_STK_GROWTH置為0時,用戶需要將堆棧的最低內(nèi)存地址傳遞給任務(wù)創(chuàng)建函數(shù),如程序清單4.7所示。
程序清單 L4.7 堆棧從下往上遞增
OS_STKTaskStack[TASK_STACK_SIZE];
OSTaskCreate(task,pdata,&TaskStack[0],prio);
當OS_CPU.H文件中的OS_STK_GROWTH置為1時,用戶需要將堆棧的最高內(nèi)存地址傳遞給任務(wù)創(chuàng)建函數(shù),如程序清單4.8所示。
程序清單 L4.8 堆棧從上往下遞減
OS_STKTaskStack[TASK_STACK_SIZE];
OSTaskCreate(task,pdata,&TaskStack[TASK_STACK_SIZE-1],prio);
這個問題會影響代碼的可移植性。 如果用戶想將代碼從支持往下遞減堆棧的處理器中移植到支持往上遞增堆棧的處理器中的話,用戶得使代碼同時適應(yīng)以上兩種情況。在這種特殊情況下,程序清單L4.7和4.8可重新寫成如程序清單L4.9所示的形式。
程序清單 L4.9 對兩個方向增長的堆棧都提供支持
OS_STKTaskStack[TASK_STACK_SIZE];
#ifOS_STK_GROWTH==0
OSTaskCreate(task,pdata,&TaskStack[0],prio);
#else
OSTaskCreate(task,pdata,&TaskStack[TASK_STACK_SIZE-1],prio);
#endif
任務(wù)所需的堆棧的容量是由應(yīng)用程序指定的。 用戶在指定堆棧大小的時候必須考慮用戶的任務(wù)所調(diào)用的所有函數(shù)的嵌套情況,任務(wù)所調(diào)用的所有函數(shù)會分配的局部變量的數(shù)目,以及所有可能的中斷服務(wù)例程嵌套的堆棧需求。另外,用戶的堆棧必須能儲存所有的CPU寄存器。
4.3 堆棧檢驗,OSTaskStkChk()
有時候決定任務(wù)實際所需的堆??臻g大小是很有必要的。因為這樣用戶就可以避免為任務(wù)分配過多的堆棧空間,從而減少自己的應(yīng)用程序代碼所需的RAM(內(nèi)存)數(shù)量。μC/OS-Ⅱ提供的OSTaskStkChk()函數(shù)可以為用戶提供這種有價值的信息。
在圖4.2中,筆者假定堆棧是從上往下遞減的(即OS_STK_GROWTH被置為1),但以下的討論也同樣適用于從下往上長的堆棧[F4.2(1)]。μC/OS-Ⅱ是通過查看堆棧本身的內(nèi)容來決定堆棧的方向的。只有內(nèi)核或是任務(wù)發(fā)出堆棧檢驗的命令時,堆棧檢驗才會被執(zhí)行,它不會自動地去不斷檢驗任務(wù)的堆棧使用情況。在堆棧檢驗時,μC/OS-Ⅱ要求在任務(wù)建立的時候堆棧中存儲的必須是0值(即堆棧被清零)[F4.2(2)]。另外,μC/OS-Ⅱ還需要知道堆棧棧底(BOS)的位置和分配給任務(wù)的堆棧的大小[F4.2(2)]。在任務(wù)建立的時候,BOS的位置及堆棧的這兩個值儲存在任務(wù)的OS_TCB中。
為了使用μC/OS-Ⅱ的堆棧檢驗功能,用戶必須要做以下幾件事情:
z 在OS_CFG.H文件中設(shè)OS_TASK_CREATE_EXT為1。
z 用OSTaskCreateExt()建立任務(wù),并給予任務(wù)比實際需要更多的內(nèi)存空間。
z 在OSTaskCreateExt()中, 將參數(shù)opt設(shè)置為OS_TASK_OPT_STK_CHK+OS_TASK_OPT_STK_CLR。注意如果用戶的程序啟動代碼清除了所有的RAM,并且從未刪除過已建立了的任務(wù),那么用戶就不必設(shè)置選項OS_TASK_OPT_STK_CLR了。這樣就會減少OSTaskCreateExt()的執(zhí)行時間。
z 將用戶想檢驗的任務(wù)的優(yōu)先級作為OSTaskStkChk()的參數(shù)并調(diào)用之。
圖 4.2 堆棧檢驗
OSTaskStkChk()順著堆棧的棧底開始計算空閑的堆??臻g大小, 具體實現(xiàn)方法是統(tǒng)計儲存值為0的連續(xù)堆棧入口的數(shù)目,直到發(fā)現(xiàn)儲存值不為0的堆棧入口[F4.2(5)]。注意堆棧入口的儲存值在進行檢驗時使用的是堆棧的數(shù)據(jù)類型(參看OS_CPU.H中的OS_STK)。換句話說,如果堆棧的入口有32位寬,對0值的比較也是按32位完成的。所用的堆棧的空間大小是指從用戶在OSTaskCreateExt()中定義的堆棧大小中減去了儲存值為0的連續(xù)堆棧入口以后的大小。OSTaskStkChk()實際上把空閑堆棧的字節(jié)數(shù)和已用堆棧的字節(jié)數(shù)放置在0S_STK_DATA數(shù)據(jù)結(jié)構(gòu)中(參看μCOS_Ⅱ.H)。注意在某個給定的時間,被檢驗的任務(wù)的堆棧指針可能會指向最初的堆棧棧頂(TOS)與堆棧最深處之間的任何位置[F4.2(7)]。 每次在調(diào)用OSTaskStkChk()的時候, 用戶也可能會因為任務(wù)還沒觸及堆棧的最深處而得到不同的堆棧的空閑空間數(shù)。
用戶應(yīng)該使自己的應(yīng)用程序運行足夠長的時間,并且經(jīng)歷最壞的堆棧使用情況,這樣才能得到正確的數(shù)。一旦OSTaskStkChk()提供給用戶最壞情況下堆棧的需求,用戶就可以重新設(shè)置堆棧的最后容量了。為了適應(yīng)系統(tǒng)以后的升級和擴展,用戶應(yīng)該多分配10%-100%的堆??臻g。在堆棧檢驗中,用戶所得到的只是一個大致的堆棧使用情況,并不能說明堆棧使用的全部實際情況。
OSTaskStkChk()函數(shù)的代碼如程序清單L4.10所示。0S_STK_DATA(參看μCOS_Ⅱ.H)數(shù)據(jù)結(jié)構(gòu)用來保存有關(guān)任務(wù)堆棧的信息。筆者打算用一個數(shù)據(jù)結(jié)構(gòu)來達到兩個目的。第一,把OSTaskStkChk()當作是查詢類型的函數(shù),并且使所有的查詢函數(shù)用同樣的方法返回,即返回查詢數(shù)據(jù)到某個數(shù)據(jù)結(jié)構(gòu)中。第二,在數(shù)據(jù)結(jié)構(gòu)中傳遞數(shù)據(jù)使得筆者可以在不改變OSTaskStkChk()的API(應(yīng)用程序編程接口)的條件下為該數(shù)據(jù)結(jié)構(gòu)增加其它域,從而擴展OSTaskStkChk()的功能?,F(xiàn)在,0S_STK_DATA只包含兩個域:OSFree和OSUsed。從代碼中用戶可看到,通過指定執(zhí)行堆棧檢驗的任務(wù)的優(yōu)先級可以調(diào)用OSTaskStkChk()。如果用戶指定0S_PRIO_SELF[L4.10(1)],那么就表明用戶想知道當前任務(wù)的堆棧信息。當然,前提是任務(wù)已經(jīng)存在[L4.10(2)]。要執(zhí)行堆棧檢驗,用戶必須已用OSTaskCreateExt()建立了任務(wù)并且已經(jīng)傳遞了選項OS_TASK_OPT_CHK[L4.10(3)]。如果所有的條件都滿足了,OSTaskStkChk()就會象前面描述的那樣從堆棧棧底開始統(tǒng)計堆棧的空閑空間[L4.10(4)]。 最后,儲存在0S_STK_DATA中的信息就被確定下來了[L4.10(5)]。注意函數(shù)所確定的是堆棧的實際空閑字節(jié)數(shù)和已被占用的字節(jié)數(shù),而不是堆棧的總字節(jié)數(shù)。當然,堆棧的實際大小(用[!--empirenews.page--]
字節(jié)表示)就是該兩項之和。
程序清單 L4.10 堆棧檢驗函數(shù)
INT8UOSTaskStkChk(INT8Uprio,OS_STK_DATA*pdata)
{
OS_TCB*ptcb;
OS_STK*pchk;
INT32Ufree;
INT32Usize;
pdata->OSFree=0;
pdata->OSUsed=0;
if(prio>OS_LOWEST_PRIO&&prio!=OS_PRIO_SELF){
return(OS_PRIO_INVALID);
}
OS_ENTER_CRITICAL();
if(prio==OS_PRIO_SELF){(1)
prio=OSTCBCur->OSTCBPrio;
}
ptcb=OSTCBPrioTbl[prio];
if(ptcb==(OS_TCB*)0){(2)
OS_EXIT_CRITICAL();
return(OS_TASK_NOT_EXIST);
}
if((ptcb->OSTCBOpt&OS_TASK_OPT_STK_CHK)==0){(3)
OS_EXIT_CRITICAL();
return(OS_TASK_OPT_ERR);
}
free=0; (4)
size=ptcb->OSTCBStkSize;
pchk=ptcb->OSTCBStkBottom;
OS_EXIT_CRITICAL();
#ifOS_STK_GROWTH==1
while(*pchk++==0){
free++;
}
#else
while(*pchk--==0){
free++;
}
#endif
pdata->OSFree=free*sizeof(OS_STK);(5)
pdata->OSUsed=(size-free)*sizeof(OS_STK);
return(OS_NO_ERR);
}
4.4刪除任務(wù),OSTaskDel()
有時候刪除任務(wù)是很有必要的。刪除任務(wù),是說任務(wù)將返回并處于休眠狀態(tài)(參看3.02,任務(wù)狀態(tài)),并不是說任務(wù)的代碼被刪除了,只是任務(wù)的代碼不再被μC/OS-Ⅱ調(diào)用。通過調(diào)用OSTaskDel()就可以完成刪除任務(wù)的功能(如程序清單L4.11所示)。OSTaskDel()一開始應(yīng)確保用戶所要刪除的任務(wù)并非是空閑任務(wù),因為刪除空閑任務(wù)是不允許的[L4.11(1)]。不過,用戶可以刪除statistic任務(wù)[L4.11(2)]。接著,OSTaskDel()還應(yīng)確保用戶不是在ISR例程中去試圖刪除一個任務(wù),因為這也是不被允許的[L4.11(3)]。調(diào)用此函數(shù)的任務(wù)可以通過指定OS_PRIO_SELF參數(shù)來刪除自己[L4.11(4)]。接下來OSTaskDel()會保證被刪除的任務(wù)是確實存在的[L4.11(3)]。如果指定的參數(shù)是OS_PRIO_SELF的話,這一判斷過程(任務(wù)是否存在)自然是可以通過的,但筆者不準備為這種情況單獨寫一段代碼,因為這樣只會增加代碼并延長程序的執(zhí)行時間。
程序清單 L4.11 刪除任務(wù)
INT8UOSTaskDel(INT8Uprio)
{
OS_TCB*ptcb;
OS_EVENT*pevent;
if(prio==OS_IDLE_PRIO){(1)
return(OS_TASK_DEL_IDLE);
}
if(prio>=OS_LOWEST_PRIO&&prio!=OS_PRIO_SELF){(2)
return(OS_PRIO_INVALID);
}
OS_ENTER_CRITICAL();
if(OSIntNesting>0){(3)
OS_EXIT_CRITICAL();
return(OS_TASK_DEL_ISR);
}
if(prio==OS_PRIO_SELF){(4)
Prio=OSTCBCur->OSTCBPrio;
}
if((ptcb=OSTCBPrioTbl[prio])!=(OS_TCB*)0){(5)
if((OSRdyTbl[ptcb->OSTCBY]&=~ptcb->OSTCBBitX)==0){(6)
OSRdyGrp&=~ptcb->OSTCBBitY;
}
if((pevent=ptcb->OSTCBEventPtr)!=(OS_EVENT*)0){(7)
if((pevent->OSEventTbl[ptcb->OSTCBY]&=~ptcb->OSTCBBitX)==0)
{
pevent->OSEventGrp&=~ptcb->OSTCBBitY;
}
}
Ptcb->OSTCBDly=0;(8)
Ptcb->OSTCBStat=OS_STAT_RDY;(9)
OSLockNesting++;(10)
OS_EXIT_CRITICAL();(11)
OSDummy();(12)
OS_ENTER_CRITICAL();
OSLockNesting--;(13)
OSTaskDelHook(ptcb);(14)
OSTaskCtr--;
OSTCBPrioTbl[prio]=(OS_TCB*)0;(15)
if(ptcb->OSTCBPrev==(OS_TCB*)0){(16)
ptcb->OSTCBNext->OSTCBPrev=(OS_TCB*)0;
OSTCBList=ptcb->OSTCBNext;
}else{
ptcb->OSTCBPrev->OSTCBNext=ptcb->OSTCBNext;
ptcb->OSTCBNext->OSTCBPrev=ptcb->OSTCBPrev;
}
ptcb->OSTCBNext=OSTCBFreeList;(17)
OSTCBFreeList=ptcb;
OS_EXIT_CRITICAL();
OSSched();(18)
return(OS_NO_ERR);
}else{
OS_EXIT_CRITICAL();
return(OS_TASK_DEL_ERR);
}
}
一旦所有條件都滿足了,OS_TCB就會從所有可能的μC/OS-Ⅱ的數(shù)據(jù)結(jié)構(gòu)中移除。OSTaskDel()分兩步完成該移除任務(wù)以減少中斷響應(yīng)時間。首先,如果任務(wù)處于就緒表中,它會直接被移除[L4.11(6)]。如果任務(wù)處于郵箱、消息隊列或信號量的等待表中,它就從自己所處的表中被移除[L4.11(7)]。接著,OSTaskDel()將任務(wù)的時鐘延遲數(shù)清零,以確保自己重新允許中斷的時候,ISR例程不會使該任務(wù)就緒[L4.11(8)]。最后,OSTaskDel()置任務(wù)的.OSTCBStat標志為OS_STAT_RDY。注意,OSTaskDel()并不是試圖使任務(wù)處于就緒狀態(tài),而是阻止其它任務(wù)或ISR例程讓該任務(wù)重新開始執(zhí)行(即避免其它任務(wù)或ISR調(diào)用OSTaskResume()[L4.11(9)])。這種情況是有可能發(fā)生的,因為OSTaskDel()會重新打開中斷,而ISR可以讓更高優(yōu)先級的任務(wù)處于就緒狀態(tài),這就可能會使用戶想刪除的任務(wù)重新開始執(zhí)行。如果不想置任務(wù)的.OSTCBStat標志為OS_STAT_RDY,就只能清除OS_STAT_SUSPEND位了(這樣代碼可能顯得更清楚,更容易理解一些),但這樣會使得處理時間稍長一些。
要被刪除的任務(wù)不會被其它的任務(wù)或ISR置于就緒狀態(tài), 因為該任務(wù)已從就緒任務(wù)表中刪除了,它不是在等待事件的發(fā)生,也不是在等待延時期滿,不能重新被執(zhí)行。為了達到刪除任務(wù)的目的,任務(wù)被置于休眠狀態(tài)。正因為這樣,OSTaskDel()必須得阻止任務(wù)調(diào)度程序[L4.11(10)]在刪除過程中切換到其它的任務(wù)中去,因為如果當前的任務(wù)正在被刪除,它不可能被再次調(diào)度!接下來,OSTaskDel()重新允許中斷以減少中斷的響應(yīng)時間[L4.11(11)]。[!--empirenews.page--]
這樣,OSTaskDel()就能處理中斷服務(wù)了,但由于它增加了 OSLockNesting,ISR執(zhí)行完后會返回到被中斷任務(wù),從而繼續(xù)任務(wù)的刪除工作。注意OSTaskDel()此時還沒有完全完成刪除任務(wù)的工作,因為它還需要從TCB鏈中解開OS_TCB,并將OS_TCB返回到空閑OS_TCB表中。
另外需要注意的是,筆者在調(diào)用OS_EXIT_CRITICAL()函數(shù)后,馬上調(diào)用了OSDummy()[L4.11(12)],該函數(shù)并不會進行任何實質(zhì)性的工作。這樣做只是因為想確保處理器在中斷允許的情況下至少執(zhí)行一個指令。對于許多處理器來說,執(zhí)行中斷允許指令會強制CPU禁止中斷直到下個指令結(jié)束!Intel80x86和ZilogZ-80處理器就是如此工作的。開中斷后馬上關(guān)中斷就等于從來沒開過中斷,當然這會增加中斷的響應(yīng)時間。因此調(diào)用OSDummy()確保在再次禁止中斷之前至少執(zhí)行了一個調(diào)用指令和一個返回指令。當然,用戶可以用宏定義將OSDummy()定義為一個空操作指令(譯者注:例如MC68HC08指令中的NOP指令) ,這樣調(diào)用OSDummy()就等于執(zhí)行了一個空操作指令,會使OSTaskDel()的執(zhí)行時間稍微縮短一點。但筆者認為這種宏定義是沒價值的,因為它會增加移植μCOS-Ⅱ的工作量。
現(xiàn)在,OSTaskDel()可以繼續(xù)執(zhí)行刪除任務(wù)的操作了。在OSTaskDel()重新關(guān)中斷后,它通過鎖定嵌套計數(shù)器(OSLockNesting)減一以重新允許任務(wù)調(diào)度[L4.11(13)]。接著,OSTaskDel()調(diào)用用戶自定義的OSTaskDelHook()函數(shù)[L4.11(14)],用戶可以在這里刪除或
釋放自定義的TCB附加數(shù)據(jù)域。 然后, OSTaskDel()減少μCOS-Ⅱ的任務(wù)計數(shù)器。 OSTaskDel()
簡單地將指向被刪除的任務(wù)的OS_TCB的指針指向NULL[L4.11(15)],從而達到將OS_TCB從優(yōu)先級表中移除的目的。再接著,OSTaskDel()將被刪除的任務(wù)的OS_TCB從OS_TCB雙向鏈表中移除[L4.11(16)]。 注意,沒有必要檢驗ptcb->OSTCBNext==0的情況, 因為OSTaskDel()不能刪除空閑任務(wù),而空閑任務(wù)就處于鏈表的末端(ptcb->OSTCBNext==0)。接下來,OS_TCB返回到空閑OS_TCB表中,并允許其它任務(wù)的建立[L4.11(17)]。最后,調(diào)用任務(wù)調(diào)度程序來查看在OSTaskDel()重新允許中斷的時候[L4.11(11)],中斷服務(wù)子程序是否曾使更高優(yōu)先級的任務(wù)處于就緒狀態(tài)[L4.11(18)]。
4.5請求刪除任務(wù),OSTaskDelReq()
有時候,如果任務(wù)A擁有內(nèi)存緩沖區(qū)或信號量之類的資源,而任務(wù)B想刪除該任務(wù),這些資源就可能由于沒被釋放而丟失。在這種情況下,用戶可以想法子讓擁有這些資源的任務(wù)在使用完資源后,先釋放資源,再刪除自己。用戶可以通過OSTaskDelReq()函數(shù)來完成該功能。
發(fā)出刪除任務(wù)請求的任務(wù)(任務(wù)B)和要刪除的任務(wù)(任務(wù)A)都需要調(diào)用OSTaskDelReq()函數(shù)。任務(wù)B的代碼如程序清單L4.12所示。任務(wù)B需要決定在怎樣的情況下請求刪除任務(wù)[L4.12(1)]。換句話說,用戶的應(yīng)用程序需要決定在什么樣的情況下刪除任務(wù)。如果任務(wù)需要被刪除,可以通過傳遞被刪除任務(wù)的優(yōu)先級來調(diào)用OSTaskDelReq()[L4.12(2)]。如果要被刪除的任務(wù)不存在(即任務(wù)已被刪除或是還沒被建立),OSTaskDelReq()返回OS_TASK_NOT_EXIST。如果OSTaskDelReq()的返回值為OS_NO_ERR,則表明請求已被接受但任務(wù)還沒被刪除。用戶可能希望任務(wù)B等到任務(wù)A刪除了自己以后才繼續(xù)進行下面的工作,這時用戶可以象筆者一樣,通過讓任務(wù)B延時一定時間來達到這個目的[L4.12(3)]。筆者延時了一個時鐘節(jié)拍。如果需要,用戶可以延時得更長一些。當任務(wù)A完全刪除自己后,[L4.12(2)]中的返回值成為0S_TASK_NOT_EXIST,此時循環(huán)結(jié)束[L4.12(4)]。
程序清單 L4.12 請求刪除其它任務(wù)的任務(wù)(任務(wù)B)
voidRequestorTask(void*pdata)
{
INT8Uerr;
pdata=pdata;
for(;;){
/* 應(yīng)用程序代碼 */
if(‘TaskToBeDeleted()‘ 需要被刪除){(1)
while(OSTaskDelReq(TASK_TO_DEL_PRIO)!=OS_TASK_NOT_EXIST){(2)
OSTimeDly(1);(3)
}
}
/*應(yīng)用程序代碼*/(4)
}
}
程序清單 L4.13 需要刪除自己的任務(wù)(任務(wù)A)
voIDTaskToBeDeleted(void*pdata)
{
INT8Uerr;
pdata=pdata;
for(;;){
/*應(yīng)用程序代碼*/
If(OSTaskDelReq(OS_PRIO_SELF)==OS_TASK_DEL_REQ){(1)
釋放所有占用的資源;(2)
釋放所有動態(tài)內(nèi)存;
OSTaskDel(OS_PRIO_SELF);(3)
}else{
/*應(yīng)用程序代碼*/
}
}
}
需要刪除自己的任務(wù)(任務(wù)A)的代碼如程序清單L4.13所示。在OS_TAB中存有一個標志,任務(wù)通過查詢這個標志的值來確認自己是否需要被刪除。這個標志的值是通過調(diào)用OSTaskDelReq(OS_PRIO_SELF)而得到的。當OSTaskDelReq()返回給調(diào)用者OS_TASK_DEL_REQ[L4.13(1)]時,則表明已經(jīng)有另外的任務(wù)請求該任務(wù)被刪除了。在這種情況下,被刪除的任務(wù)會釋放它所擁有的所用資源[L4.13(2)],并且調(diào)用OSTaskDel(OS_PRIO_SELF)來刪除自己[L4.13(3)]。前面曾提到過,任務(wù)的代碼沒有被真正的刪除,而只是μC/OS-Ⅱ不再理會該任務(wù)代碼,換句話說,就是任務(wù)的代碼不會再運行了。
但是,用戶可以通過調(diào)用OSTaskCreate()或OSTaskCreateExt()函數(shù)重新建立該任務(wù)。OSTaskDelReq()的代碼如程序清單L4.14所示。通常OSTaskDelReq()需要檢查臨界條件。首先,如果正在刪除的任務(wù)是空閑任務(wù),OSTaskDelReq()會報錯并返回[L4.14(1)]。接著,它要保證調(diào)用者請求刪除的任務(wù)的優(yōu)先級是有效的[L4.14(2)]。如果調(diào)用者就是被刪除任務(wù)本身,存儲在OS_TCB中的標志將會作為返回值[L4.14(3)]。如果用戶用優(yōu)先級而不是OS_PRIO_SELF指定任務(wù),并且任務(wù)是存在的[L4.14(4)],OSTaskDelReq()就會設(shè)置任務(wù)的內(nèi)部標志[L4.14(5)]。如果任務(wù)不存在,OSTaskDelReq()則會返回OS_TASK_NOT_EXIST,表明任務(wù)可能已經(jīng)刪除自己了[L4.14(6)]。
程序清單 L4.14 OSTaskDelReq().
INT8UOSTaskDelReq(INT8Uprio)
{
BOOLEANstat;
INT8Uerr;
OS_TCB*ptcb;
if(prio==OS_IDLE_PRIO){(1)
return(OS_TASK_DEL_IDLE);
}
if(prio>=OS_LOWEST_PRIO&&prio!=OS_PRIO_SELF){[!--empirenews.page--]
(2)
return(OS_PRIO_INVALID);
}
if(prio==OS_PRIO_SELF){(3)
OS_ENTER_CRITICAL();
stat=OSTCBCur->OSTCBDelReq;
OS_EXIT_CRITICAL();
return(stat);
}
else{
OS_ENTER_CRITICAL();
if((ptcb=OSTCBPrioTbl[prio])!=(OS_TCB*)0){(4)
ptcb->OSTCBDelReq=OS_TASK_DEL_REQ;(5)
err=OS_NO_ERR;
}else{
err=OS_TASK_NOT_EXIST;(6)
}
OS_EXIT_CRITICAL();
return(err);
}
}
4.6改變?nèi)蝿?wù)的優(yōu)先級,OSTaskChangePrio()
在用戶建立任務(wù)的時候會分配給任務(wù)一個優(yōu)先級。在程序運行期間,用戶可以通過調(diào)用OSTaskChangePrio()來改變?nèi)蝿?wù)的優(yōu)先級。換句話說,就是μC/OS-Ⅱ允許用戶動態(tài)的改變?nèi)蝿?wù)的優(yōu)先級。
OSTaskChangePrio()的代碼如程序清單L4.15所示。用戶不能改變空閑任務(wù)的優(yōu)先級[L4.15(1)],但用戶可以改變調(diào)用本函數(shù)的任務(wù)或者其它任務(wù)的優(yōu)先級。為了改變調(diào)用本函數(shù)的任務(wù)的優(yōu)先級,用戶可以指定該任務(wù)當前的優(yōu)先級或OS_PRIO_SELF,
OSTaskChangePrio()會決定該任務(wù)的優(yōu)先級。用戶還必須指定任務(wù)的新(即想要的)優(yōu)先級。因為μC/OS-Ⅱ不允許多個任務(wù)具有相同的優(yōu)先級,所以O(shè)STaskChangePrio()需要檢驗新優(yōu)先級是否是合法的(即不存在具有新優(yōu)先級的任務(wù))[L4.15(2)]。如果新優(yōu)先級是合法的,μC/OS-Ⅱ通過將某些東西儲存到OSTCBPrioTbl[newprio]中保留這個優(yōu)先級[L4.15(3)]。如此就使得OSTaskChangePrio()可以重新允許中斷,因為此時其它任務(wù)已經(jīng)不可能建立擁有該優(yōu)先級的任務(wù),也不能通過指定相同的新優(yōu)先級來調(diào)用OSTaskChangePrio()。接下來OSTaskChangePrio()可以預(yù)先計算新優(yōu)先級任務(wù)的OS_TCB中的某些值[L4.15(4)]。而這些值用來將任務(wù)放入就緒表或從該表中移除(參看3.04,就緒表)。
接著,OSTaskChangePrio()檢驗?zāi)壳暗娜蝿?wù)是否想改變它的優(yōu)先級[L4.15(5)]。然后,TaskChangePrio()檢查想要改變優(yōu)先級的任務(wù)是否存在[L4.15(6)]。很明顯,如果要改變優(yōu)先級的任務(wù)就是當前任務(wù),這個測試就會成功。但是,如果OSTaskChangePrio()想要改變優(yōu)先級的任務(wù)不存在,它必須將保留的新優(yōu)先級放回到優(yōu)先級表OSTCBPrioTbl[]中[L4.15(17)],并返回給調(diào)用者一個錯誤碼。
現(xiàn)在, OSTaskChangePrio()可以通過插入NULL指針將指向當前任務(wù)OS_TCB的指針從優(yōu)先級表中移除了[L4.15(7)]。這就使得當前任務(wù)的舊的優(yōu)先級可以重新使用了。接著,我們檢驗一下OSTaskChangePrio()想要改變優(yōu)先級的任務(wù)是否就緒[L4.15(8)]。如果該任務(wù)處于就緒狀態(tài),它必須在當前的優(yōu)先級下從就緒表中移除[L4.15(9)],然后在新的優(yōu)先級下插入到就緒表中[L4.15(10)]。這兒需要注意的是,OSTaskChangePrio()所用的是重新計算的值[L4.15(4)]將任務(wù)插入就緒表中的。
如果任務(wù)已經(jīng)就緒,它可能會正在等待一個信號量、一封郵件或是一個消息隊列。如果OSTCBEventPtr非空(不等于NULL)[L4.15(8)],OSTaskChangePrio()就會知道任務(wù)正在等待以上的某件事。如果任務(wù)在等待某一事件的發(fā)生,OSTaskChangePrio()必須將任務(wù)從事件控制塊(參看6.00,事件控制塊)的等待隊列(在舊的優(yōu)先級下)中移除。并在新的優(yōu)先級下將事件插入到等待隊列中[L4.15(12)]。 任務(wù)也有可能正在等待延時的期滿(參看第五章-任務(wù)管理)或是被掛起(參看4.07,掛起任務(wù),OSTaskSuspend())。在這些情況下,從L4.15(8)到L4.15(12)這幾行可以略過。
接著,OSTaskChangePrio()將指向任務(wù)OS_TCB的指針存到OSTCBPrioTbl[]中[L4.15(13)]。 新的優(yōu)先級被保存在OS_TCB中[L4.15(14)], 重新計算的值也被保存在OS_TCB中[L4.15(15)]。OSTaskChangePrio()完成了關(guān)鍵性的步驟后,在新的優(yōu)先級高于舊的優(yōu)先級或新的優(yōu)先級高于調(diào)用本函數(shù)的任務(wù)的優(yōu)先級情況下,任務(wù)調(diào)度程序就會被調(diào)用[L4.15(16)]。
程序清單 L4.15 OSTaskChangePrio().
INT8UOSTaskChangePrio(INT8Uoldprio,INT8Unewprio)
{
OS_TCB*ptcb;
OS_EVENT*pevent;
INT8Ux;
INT8Uy;
INT8Ubitx;
INT8Ubity;
if((oldprio>=OS_LOWEST_PRIO&&oldprio!=OS_PRIO_SELF)||(1)
newprio>=OS_LOWEST_PRIO){
return(OS_PRIO_INVALID);
}
OS_ENTER_CRITICAL();
if(OSTCBPrioTbl[newprio]!=(OS_TCB*)0){(2)
OS_EXIT_CRITICAL();
return(OS_PRIO_EXIST);
}else{
OSTCBPrioTbl[newprio]=(OS_TCB*)1;(3)
OS_EXIT_CRITICAL();
y=newprio>>3;(4)
bity=OSMapTbl[y];
x=newprio&0x07;
bitx=OSMapTbl[x];
OS_ENTER_CRITICAL();
if(oldprio==OS_PRIO_SELF){(5)
oldprio=OSTCBCur->OSTCBPrio;
}
if((ptcb=OSTCBPrioTbl[oldprio])!=(OS_TCB*)0){(6)
OSTCBPrioTbl[oldprio]=(OS_TCB*)0;(7)
if(OSRdyTbl[ptcb->OSTCBY]&ptcb->OSTCBBitX){(8)
if((OSRdyTbl[ptcb->OSTCBY]&=~ptcb->OSTCBBitX)==0){(9)
OSRdyGrp&=~ptcb->OSTCBBitY;
}
OSRdyGrp|=bity;(10)
OSRdyTbl[y]|=bitx;
}else{
if((pevent=ptcb->OSTCBEventPtr)!=(OS_EVENT*)0){(11)
if((pevent->OSEventTbl[ptcb->OSTCBY]&=
~ptcb->OSTCBBitX)==0){
pevent->OSEventGrp&=~ptcb->OSTCBBitY;
}
pevent->OSEventGrp|=bity;(12)
pevent->OSEventTbl[y]|=bitx;
}
}
OSTCBPrioTbl[newprio]=ptcb;(13)
ptcb->OSTCBPrio=newprio;(14)
ptcb->OSTCBY=y;(15)
ptcb->OSTCBX=x;
ptcb->OSTCBBitY=bity;
ptcb->OSTCBBitX=bitx;
OS_EXIT_CRITICAL();
OSSched();(16)
return(OS_NO_ERR);[!--empirenews.page--]
}else{
OSTCBPrioTbl[newprio]=(OS_TCB*)0;(17)
OS_EXIT_CRITICAL();
return(OS_PRIO_ERR);
}
}
}
4.7 掛起任務(wù),OSTaskSuspend()
有時候?qū)⑷蝿?wù)掛起是很有用的。掛起任務(wù)可通過調(diào)用OSTaskSuspend()函數(shù)來完成。被掛起的任務(wù)只能通過調(diào)用OSTaskResume()函數(shù)來恢復(fù)。任務(wù)掛起是一個附加功能。也就是說,如果任務(wù)在被掛起的同時也在等待延時的期滿,那么,掛起操作需要被取消,而任務(wù)繼續(xù)等待延時期滿,并轉(zhuǎn)入就緒狀態(tài)。任務(wù)可以掛起自己或者其它任務(wù)。
OSTaskSuspend()函數(shù)的代碼如程序清單L4.16所示。通常OSTaskSuspend()需要檢驗臨界條件。首先,OSTaskSuspend()要確保用戶的應(yīng)用程序不是在掛起空閑任務(wù)[L4.16(1)],接著確認用戶指定優(yōu)先級是有效的[L4.16(2)]。記住最大的有效的優(yōu)先級數(shù)(即最低的優(yōu)先級)是OS_LOWEST_PRIO。注意,用戶可以掛起統(tǒng)計任務(wù)(statistic) 。可能用戶已經(jīng)注意到了,第一個測試[L4.16(1)]在[L4.16(2)]中被重復(fù)了。筆者這樣做是為了能與μC/OS兼容。
第一個測試能夠被移除并可以節(jié)省一點程序處理的時間,但是,這樣做的意義不大,所以筆者決定留下它。
接著, OSTaskSuspend()檢驗用戶是否通過指定 OS_PRIO_SELF來掛起調(diào)用本函數(shù)的任務(wù)本身[L4.16(3)]。用戶也可以通過指定優(yōu)先級來掛起調(diào)用本函數(shù)的任務(wù)[L4.16(4)]。在這兩種情況下,任務(wù)調(diào)度程序都需要被調(diào)用。這就是筆者為什么要定義局部變量self的原因,該變量在適當?shù)那闆r下會被測試。如果用戶沒有掛起調(diào)用本函數(shù)的任務(wù),OSTaskSuspend()就沒有必要運行任務(wù)調(diào)度程序,因為正在掛起的是較低優(yōu)先級的任務(wù)。
然后,OSTaskSuspend()檢驗要掛起的任務(wù)是否存在[L4.16(5)]。如果該任務(wù)存在的話,它就會從就緒表中被移除[L4.16(6)]。注意要被掛起的任務(wù)有可能沒有在就緒表中,因為它有可能在等待事件的發(fā)生或延時的期滿。在這種情況下,要被掛起的任務(wù)在OSRdyTbl[]中對應(yīng)的位已被清除了(即為0)。再次清除該位,要比先檢驗該位是否被清除了再在它沒被清除時清除它快得多,所以筆者沒有檢驗該位而直接清除它?,F(xiàn)在,OSTaskSuspend()就可以在任務(wù)的OS_TCB中設(shè)置OS_STAT_SUSPEND標志了,以表明任務(wù)正在被掛起[L4.16(7)]。最后,OSTaskSuspend()只有在被掛起的任務(wù)是調(diào)用本函數(shù)的任務(wù)本身的情況下才調(diào)用任務(wù)調(diào)度程序[L4.16(8)]。
程序清單 L4.16 OSTaskSuspend().
INT8UOSTaskSuspend(INT8Uprio)
{
BOOLEANself;
OS_TCB*ptcb;
if(prio==OS_IDLE_PRIO){(1)
return(OS_TASK_SUSPEND_IDLE);
}
if(prio>=OS_LOWEST_PRIO&&prio!=OS_PRIO_SELF){
(2)
return(OS_PRIO_INVALID);
}
OS_ENTER_CRITICAL();
if(prio==OS_PRIO_SELF){(3)
prio=OSTCBCur->OSTCBPrio;
self=TRUE;
}elseif(prio==OSTCBCur->OSTCBPrio){(4)
self=TRUE;
}else{
self=FALSE;
}
if((ptcb=OSTCBPrioTbl[prio])==(OS_TCB*)0){(5)
OS_EXIT_CRITICAL();
return(OS_TASK_SUSPEND_PRIO);
}else{
if((OSRdyTbl[ptcb->OSTCBY]&=~ptcb->OSTCBBitX)==0){(6)
OSRdyGrp&=~ptcb->OSTCBBitY;
}
ptcb->OSTCBStat|=OS_STAT_SUSPEND;(7)
OS_EXIT_CRITICAL();
if(self==TRUE){(8)
OSSched();
}
return(OS_NO_ERR);
}
}
4.8 恢復(fù)任務(wù),OSTaskResume()
在上一節(jié)中曾提到過,被掛起的任務(wù)只有通過調(diào)用OSTaskResume()才能恢復(fù)。OSTaskResume()函數(shù)的代碼如程序清單L4.17所示。 因為OSTaskSuspend()不能掛起空閑任務(wù),所以必須得確認用戶的應(yīng)用程序不是在恢復(fù)空閑任務(wù)[L4.17(1)]。注意,這個測試也可以確保用戶不是在恢復(fù)優(yōu)先級為OS_PRIO_SELF的任務(wù)(OS_PRIO_SELF被定義為0xFF,它總是比OS_LOWEST_PRIO大)。
要恢復(fù)的任務(wù)必須是存在的,因為用戶要需要操作它的任務(wù)控制塊OS_TCB[L4.17(2)],并且該任務(wù)必須是被掛起的[L4.17(3)]。OSTaskResume()是通過清除OSTCBStat域中的OS_STAT_SUSPEND位來取消掛起的[L4.17(4)]。要使任務(wù)處于就緒狀態(tài),OS_TCBDly域必須為0[L4.17(5)],這是因為在OSTCBStat中沒有任何標志表明任務(wù)正在等待延時的期滿。只有當以上兩個條件都滿足的時候,任務(wù)才處于就緒狀態(tài)[L4.17(6)]。最后,任務(wù)調(diào)度程序會檢查被恢復(fù)的任務(wù)擁有的優(yōu)先級是否比調(diào)用本函數(shù)的任務(wù)的優(yōu)先級高[L4.17(7)]。
程序清單 L4.17 OSTaskResume().
INT8UOSTaskResume(INT8Uprio)
{
OS_TCB*ptcb;
If(prio>=OS_LOWEST_PRIO){(1)
return(OS_PRIO_INVALID);
}
OS_ENTER_CRITICAL();
If((ptcb=OSTCBPrioTbl[prio])==(OS_TCB*)0){(2)
OS_EXIT_CRITICAL();
return(OS_TASK_RESUME_PRIO);
}else{
if(ptcb->OSTCBStat&OS_STAT_SUSPEND){(3)
if(((ptcb->OSTCBStat&=~OS_STAT_SUSPEND)==OS_STAT_RDY)&&(4)
(ptcb->OSTCBDly==0)){(5)
OSRdyGrp|=ptcb->OSTCBBitY;(6)
OSRdyTbl[ptcb->OSTCBY]|=ptcb->OSTCBBitX;
OS_EXIT_CRITICAL();
OSSched();(7)
}else{
OS_EXIT_CRITICAL();
}
return(OS_NO_ERR);
}else{
OS_EXIT_CRITICAL();
return(OS_TASK_NOT_SUSPENDED);
}
}
}
4.9 獲得有關(guān)任務(wù)的信息,OSTaskQuery()
用戶的應(yīng)用程序可以通過調(diào)用OSTaskQuery()來獲得自身或其它應(yīng)用任務(wù)的信息。實際上,OSTaskQuery()獲得的是對應(yīng)任務(wù)的 OS_TCB中內(nèi)容的拷貝。用戶能訪問的OS_TCB的數(shù)據(jù)域的多少決定于用戶的應(yīng)用程序的配置(參看OS_CFG.H)。由于μC/OS-Ⅱ是可裁剪的,它只包括那些用戶的應(yīng)用程序所要求的屬性和功能。[!--empirenews.page--]
要調(diào)用OSTaskQuery(),如程序清單L4.18中所示的那樣,用戶的應(yīng)用程序必須要為OS_TCB分配存儲空間。這個OS_TCB與μC/OS-Ⅱ分配的OS_TCB是完全不同的數(shù)據(jù)空間。在調(diào)用了OSTaskQuery()后,這個OS_TCB包含了對應(yīng)任務(wù)的OS_TCB的副本。用戶必須十分小心地處理OS_TCB中指向其它OS_TCB的指針(即OSTCBNext與OSTCBPrev);用戶不要試圖去改變這些指針! 一般來說, 本函數(shù)只用來了解任務(wù)正在干什么——本函數(shù)是有用的調(diào)試工具。
程序清單 L4.18 得到任務(wù)的信息
OS_TCBMyTaskData;
voidMyTask(void*pdata)
{
pdata=pdata;
for(;;){
/* 用戶代碼 */
err=OSTaskQuery(10,&MyTaskData);
/*Examineerrorcode..*/
/* 用戶代碼 */
}
}
OSTaskQuery()的代碼如程序清單L4.19所示。注意,筆者允許用戶查詢所有的任務(wù),包括空閑任務(wù)[L4.19(1)]。 用戶尤其需要注意的是不要改變OSTCBNext與OSTCBPrev的指向。
通常,OSTaskQuery()需要檢驗用戶是否想知道當前任務(wù)的有關(guān)信息[L4.19(2)]以及該任務(wù)是否已經(jīng)建立了[L4.19(3)]。 所有的域是通過賦值語句一次性復(fù)制的而不是一個域一個域地復(fù)制的[L4.19(4)]。這樣復(fù)制會比較快一點,因為編譯器大多都能夠產(chǎn)生內(nèi)存拷貝指令。
程序清單 L4.19 OSTaskQuery().
INT8UOSTaskQuery(INT8Uprio,OS_TCB*pdata)
{
OS_TCB*ptcb;
if(prio>OS_LOWEST_PRIO&&prio!=OS_PRIO_SELF){(1)
return(OS_PRIO_INVALID);
}
OS_ENTER_CRITICAL();
if(prio==OS_PRIO_SELF){(2)
prio=OSTCBCur->OSTCBPrio;
}
if((ptcb=OSTCBPrioTbl[prio])==(OS_TCB*)0){(3)
OS_EXIT_CRITICAL();
return(OS_PRIO_ERR);
}
*pdata=*ptcb;(4)
OS_EXIT_CRITICAL();
return(OS_NO_ERR);
}