μC/OS-II的時(shí)間管理
在 3.10節(jié)時(shí)鐘節(jié)拍中曾提到,μC/OS-Ⅱ(其它內(nèi)核也一樣)要求用戶(hù)提供定時(shí)中斷來(lái)實(shí)現(xiàn)延時(shí)與超時(shí)控制等功能。這個(gè)定時(shí)中斷叫做時(shí)鐘節(jié)拍,它應(yīng)該每秒發(fā)生10至100次。時(shí)鐘節(jié)拍的實(shí)際頻率是由用戶(hù)的應(yīng)用程序決定的。時(shí)鐘節(jié)拍的頻率越高,系統(tǒng)的負(fù)荷就越重。
3.10 節(jié)討論了時(shí)鐘的中斷服務(wù)子程序和節(jié)時(shí)鐘節(jié)函數(shù) OSTimeTIck——該函數(shù)用于通知μC/OS-Ⅱ發(fā)生了時(shí)鐘節(jié)拍中斷。本章主要講述五個(gè)與時(shí)鐘節(jié)拍有關(guān)的系統(tǒng)服務(wù):
z OSTimeDly()
z OSTimeDlyHMSM()
z OSTimeDlyResume()
z OSTimeGet()
z OSTimeSet()
本章所提到的函數(shù)可以在OS_TIME.C文件中找到。
5.0任務(wù)延時(shí)函數(shù),OSTimeDly()
μC/OS-Ⅱ提供了這樣一個(gè)系統(tǒng)服務(wù):申請(qǐng)?jiān)摲?wù)的任務(wù)可以延時(shí)一段時(shí)間,這段時(shí)間的長(zhǎng)短是用時(shí)鐘節(jié)拍的數(shù)目來(lái)確定的。實(shí)現(xiàn)這個(gè)系統(tǒng)服務(wù)的函數(shù)叫做OSTimeDly()。調(diào)用該函數(shù)會(huì)使μC/OS-Ⅱ進(jìn)行一次任務(wù)調(diào)度,并且執(zhí)行下一個(gè)優(yōu)先級(jí)最高的就緒態(tài)任務(wù)。任務(wù)調(diào)用OSTimeDly()后,一旦規(guī)定的時(shí)間期滿(mǎn)或者有其它的任務(wù)通過(guò)調(diào)用OSTimeDlyResume()取消了延時(shí),它就會(huì)馬上進(jìn)入就緒狀態(tài)。注意,只有當(dāng)該任務(wù)在所有就緒任務(wù)中具有最高的優(yōu)先級(jí)時(shí),它才會(huì)立即運(yùn)行。
程序清單L5.1所示的是任務(wù)延時(shí)函數(shù)OSTimeDly()的代碼。用戶(hù)的應(yīng)用程序是通過(guò)提供延時(shí)的時(shí)鐘節(jié)拍數(shù)——一個(gè)1到65535之間的數(shù),來(lái)調(diào)用該函數(shù)的。如果用戶(hù)指定0值[L5.1(1)],則表明用戶(hù)不想延時(shí)任務(wù),函數(shù)會(huì)立即返回到調(diào)用者。非0值會(huì)使得任務(wù)延時(shí)函數(shù)OSTimeDly()將當(dāng)前任務(wù)從就緒表中移除[L5.1(2)]。接著,這個(gè)延時(shí)節(jié)拍數(shù)會(huì)被保存在當(dāng)前任務(wù)的OS_TCB中[L5.1(3)],并且通過(guò)OSTimeTick()每隔一個(gè)時(shí)鐘節(jié)拍就減少一個(gè)延時(shí)節(jié)拍數(shù)。最后,既然任務(wù)已經(jīng)不再處于就緒狀態(tài),任務(wù)調(diào)度程序會(huì)執(zhí)行下一個(gè)優(yōu)先級(jí)最高的就緒任務(wù)。
程序清單 L5.1 OSTimeDly().
voidOSTimeDly(INT16Uticks)
{
if(ticks>0){(1)
OS_ENTER_CRITICAL();
if((OSRdyTbl[OSTCBCur->OSTCBY]&=~OSTCBCur->OSTCBBitX)==0)
{(2)
OSRdyGrp&=~OSTCBCur->OSTCBBitY;
}
OSTCBCur->OSTCBDly=ticks;(3)
OS_EXIT_CRITICAL();
OSSched();(4)
}
}
清楚地認(rèn)識(shí)0到一個(gè)節(jié)拍之間的延時(shí)過(guò)程是非常重要的。換句話(huà)說(shuō),如果用戶(hù)只想延時(shí)一個(gè)時(shí)鐘節(jié)拍,而實(shí)際上是在0到一個(gè)節(jié)拍之間結(jié)束延時(shí)。即使用戶(hù)的處理器的負(fù)荷不是很重,這種情況依然是存在的。圖F5.1詳細(xì)說(shuō)明了整個(gè)過(guò)程。系統(tǒng)每隔10ms發(fā)生一次時(shí)鐘節(jié)拍中斷[F5.1(1)]。假如用戶(hù)沒(méi)有執(zhí)行其它的中斷并且此時(shí)的中斷是開(kāi)著的,時(shí)鐘節(jié)拍中斷服務(wù)就會(huì)發(fā)生[F5.1(2)]。也許用戶(hù)有好幾個(gè)高優(yōu)先級(jí)的任務(wù)(HPT)在等待延時(shí)期滿(mǎn),它們會(huì)接著執(zhí)行[F5.1(3)]。接下來(lái),圖5.1中所示的低優(yōu)先級(jí)任務(wù)(LPT)會(huì)得到執(zhí)行的機(jī)會(huì),該任務(wù)在執(zhí)行完后馬上調(diào)用[F5.1(4)]所示的 OSTimeDly(1)。 μC/OS-Ⅱ會(huì)使該任務(wù)處于休眠狀態(tài)直至下一個(gè)節(jié)拍的到來(lái)。當(dāng)下一個(gè)節(jié)拍到來(lái)后,時(shí)鐘節(jié)拍中斷服務(wù)子程序會(huì)執(zhí)行[F5.1(5)],但是這一次由于沒(méi)有高優(yōu)先級(jí)的任務(wù)被執(zhí)行,μC/OS-Ⅱ會(huì)立即執(zhí)行申請(qǐng)延時(shí)一個(gè)時(shí)鐘節(jié)拍的任務(wù)[F5.1(6)]。正如用戶(hù)所看到的,該任務(wù)實(shí)際的延時(shí)少于一個(gè)節(jié)拍!在負(fù)荷很重的系統(tǒng)中,任務(wù)甚至有可能會(huì)在時(shí)鐘中斷即將發(fā)生時(shí)調(diào)用OSTimeDly(1),在這種情況下,任務(wù)幾乎就沒(méi)有得到任何延時(shí),因?yàn)槿蝿?wù)馬上又被重新調(diào)度了。如果用戶(hù)的應(yīng)用程序至少得延時(shí)一個(gè)節(jié)拍,必須要調(diào)用OSTimeDly(2),指定延時(shí)兩個(gè)節(jié)拍!
Figure5.1DelayresolutiON.
5.1按時(shí)分秒延時(shí)函數(shù) OSTimeDlyHMSM()
OSTimeDly()雖然是一個(gè)非常有用的函數(shù),但用戶(hù)的應(yīng)用程序需要知道延時(shí)時(shí)間對(duì)應(yīng)的時(shí)鐘節(jié)拍的數(shù)目。用戶(hù)可以使用定義全局常數(shù)OS_TICKS_PER_SEC(參看OS_CFG.H)的方法將時(shí)間轉(zhuǎn)換成時(shí)鐘段,但這種方法有時(shí)顯得比較愚笨。筆者增加了OSTimeDlyHMSM()函數(shù)后,用戶(hù)就可以按小時(shí)(H)、分(M)、秒(S)和毫秒(m)來(lái)定義時(shí)間了,這樣會(huì)顯得更自然些。與OSTimeDly()一樣,調(diào)用OSTimeDlyHMSM()函數(shù)也會(huì)使μC/OS-Ⅱ進(jìn)行一次任務(wù)調(diào)度,并且執(zhí)行下一個(gè)優(yōu)先級(jí)最高的就緒態(tài)任務(wù)。任務(wù)調(diào)用OSTimeDlyHMSM()后,一旦規(guī)定的時(shí)間期滿(mǎn)或者有其它的任務(wù)通過(guò)調(diào)用OSTimeDlyResume()取消了延時(shí)(參看5.02,恢復(fù)延時(shí)的任務(wù)OSTimeDlyResume()),它就會(huì)馬上處于就緒態(tài)。同樣,只有當(dāng)該任務(wù)在所有就緒態(tài)任務(wù)中具有最高的優(yōu)先級(jí)時(shí),它才會(huì)立即運(yùn)行。
程序清單L5.2所示的是OSTimeDlyHMSM()的代碼。從中可以看出,應(yīng)用程序是通過(guò)用小時(shí)、分、秒和毫秒指定延時(shí)來(lái)調(diào)用該函數(shù)的。在實(shí)際應(yīng)用中,用戶(hù)應(yīng)避免使任務(wù)延時(shí)過(guò)長(zhǎng)的時(shí)間, 因?yàn)閺娜蝿?wù)中獲得一些反饋行為(如減少計(jì)數(shù)器, 清除LED等等)經(jīng)常是很不錯(cuò)的事。
但是,如果用戶(hù)確實(shí)需要延時(shí)長(zhǎng)時(shí)間的話(huà),μC/OS-Ⅱ可以將任務(wù)延時(shí)長(zhǎng)達(dá)256個(gè)小時(shí)(接近11天)。
OSTimeDlyHMSM()一開(kāi)始先要檢驗(yàn)用戶(hù)是否為參數(shù)定義了有效的值[L5.2(1)]。與OSTimeDly()一樣,即使用戶(hù)沒(méi)有定義延時(shí),OSTimeDlyHMSM()也是存在的[L5.2(9)]。因?yàn)?mu;C/OS-Ⅱ只知道節(jié)拍,所以節(jié)拍總數(shù)是從指定的時(shí)間中計(jì)算出來(lái)的[L5.2(3)]。很明顯,程序清單L5.2中的程序并不是十分有效的。筆者只是用這種方法告訴大家一個(gè)公式,這樣用戶(hù)就可以知道怎樣計(jì)算總的節(jié)拍數(shù)了。真正有意義的只是OS_TICKS_PER_SEC。[L5.2(3)]決定了最接近需要延遲的時(shí)間的時(shí)鐘節(jié)拍總數(shù)。500/OS_TICKS_PER_SECOND的值基本上與0.5個(gè)節(jié)拍對(duì)應(yīng)的毫秒數(shù)相同。例如,若將時(shí)鐘頻率(OS_TICKS_PER_SEC)設(shè)置成100Hz(10ms),4ms的延時(shí)不會(huì)產(chǎn)生任何延時(shí)!而5ms的延時(shí)就等于延時(shí)10ms。
μC/OS-Ⅱ支持的延時(shí)最長(zhǎng)為65,535個(gè)節(jié)拍。要想支持更長(zhǎng)時(shí)間的延時(shí),如L5.2(2)所示,OSTimeDlyHMSM()確定了用戶(hù)想延時(shí)多少次超過(guò)65,535個(gè)節(jié)拍的數(shù)目[L5.2(4)]和剩下的節(jié)拍數(shù)[L5.2(5)]。例如,若OS_TICKS_PER_SEC的值為100,用戶(hù)想延時(shí)15分鐘,則OSTimeDlyHMSM()會(huì)延時(shí)15x60x100=90,000個(gè)時(shí)鐘。這個(gè)延時(shí)會(huì)被分割成兩次32,768個(gè)節(jié)拍的延時(shí)(因?yàn)橛脩?hù)只能延時(shí)65,535個(gè)節(jié)拍而不是65536個(gè)節(jié)拍)和一次24,464個(gè)節(jié)拍的延時(shí)。在這種情況下,OSTimeDlyHMSM()首先考慮剩下的節(jié)拍,然后是超過(guò)65,535的節(jié)拍數(shù)[L5.2(7)和(8)](即兩個(gè)32,768個(gè)節(jié)拍延時(shí))。[!--empirenews.page--]
程序清單 L5.2 OSTimeDlyHMSM().
INT8UOSTimeDlyHMSM(INT8Uhours,INT8Uminutes,INT8Useconds,INT16U
milli)
{
INT32Uticks;
INT16Uloops;
if(hours>0|| minutes>0|| seconds>0|| milli>0){(1)
if(minutes>59){
return(OS_TIME_INVALID_MINUTES);
}
if(seconds>59){
return(OS_TIME_INVALID_SECONDS);
}
If(milli>999){
return(OS_TIME_INVALID_MILLI);
}
ticks=(INT32U)hours*3600L*OS_TICKS_PER_SEC(2)
+(INT32U)minutes*60L*OS_TICKS_PER_SEC
+(INT32U)seconds*OS_TICKS_PER_SEC
+OS_TICKS_PER_SEC*((INT32U)milli
+500L/OS_TICKS_PER_SEC)/1000L;(3)
loops=ticks/65536L;(4)
ticks=ticks%65536L;(5)
OSTimeDly(ticks);(6)
while(loops>0){(7)
OSTimeDly(32768);(8)
OSTimeDly(32768);
loops--;
}
return(OS_NO_ERR);
}else{
return(OS_TIME_ZERO_DLY);(9)
}
}
由于OSTimeDlyHMSM()的具體實(shí)現(xiàn)方法,用戶(hù)不能結(jié)束延時(shí)調(diào)用OSTimeDlyHMSM()要求延時(shí)超過(guò)65535個(gè)節(jié)拍的任務(wù)。換句話(huà)說(shuō),如果時(shí)鐘節(jié)拍的頻率是100Hz,用戶(hù)不能讓調(diào)用OSTimeDlyHMSM(0,10,55,350)或更長(zhǎng)延遲時(shí)間的任務(wù)結(jié)束延時(shí)。
5.2讓處在延時(shí)期的任務(wù)結(jié)束延時(shí),OSTimeDlyResume()
μC/OS-Ⅱ允許用戶(hù)結(jié)束延時(shí)正處于延時(shí)期的任務(wù)。延時(shí)的任務(wù)可以不等待延時(shí)期滿(mǎn),而是通過(guò)其它任務(wù)取消延時(shí)來(lái)使自己處于就緒態(tài)。這可以通過(guò)調(diào)用OSTimeDlyResume()和指定要恢復(fù)的任務(wù)的優(yōu)先級(jí)來(lái)完成。實(shí)際上,OSTimeDlyResume()也可以喚醒正在等待事件(參看第六章——任務(wù)間的通訊和同步)的任務(wù),雖然這一點(diǎn)并沒(méi)有提到過(guò)。在這種情況下,等待事件發(fā)生的任務(wù)會(huì)考慮是否終止等待事件。
OSTimeDlyResume()的代碼如程序清單L5.3所示,它首先要確保指定的任務(wù)優(yōu)先級(jí)有效[L5.3(1)]。 接著, OSTimeDlyResume()要確認(rèn)要結(jié)束延時(shí)的任務(wù)是確實(shí)存在的[L5.3(2)]。
如果任務(wù)存在, OSTimeDlyResume()會(huì)檢驗(yàn)任務(wù)是否在等待延時(shí)期滿(mǎn)[L5.3(3)]。 只要OS_TCB
域中的OSTCBDly包含非0值就表明任務(wù)正在等待延時(shí)期滿(mǎn),因?yàn)槿蝿?wù)調(diào)用了OSTimeDly(),OSTimeDlyHMSM()或其它在第六章中所描述的PEND函數(shù)。然后延時(shí)就可以通過(guò)強(qiáng)制命令OSTCBDly為0來(lái)取消[L5.3(4)]。延時(shí)的任務(wù)有可能已被掛起了,這樣的話(huà),任務(wù)只有在沒(méi)有被掛起的情況下才能處于就緒狀態(tài)[L5.3(5)]。當(dāng)上面的條件都滿(mǎn)足后,任務(wù)就會(huì)被放在就緒表中[L5.3(6)]。這時(shí),OSTimeDlyResume()會(huì)調(diào)用任務(wù)調(diào)度程序來(lái)看被恢復(fù)的任務(wù)是否擁有比當(dāng)前任務(wù)更高的優(yōu)先級(jí)[L5.3(7)]。這會(huì)導(dǎo)致任務(wù)的切換。
程序清單 L5.3 恢復(fù)正在延時(shí)的任務(wù)
INT8UOSTimeDlyResume(INT8Uprio)
{
OS_TCB*ptcb;
if(prio>=OS_LOWEST_PRIO){(1)
return(OS_PRIO_INVALID);
}
OS_ENTER_CRITICAL();
ptcb=(OS_TCB*)OSTCBPrioTbl[prio];
if(ptcb!=(OS_TCB*)0){(2)
if(ptcb->OSTCBDly!=0){(3)
ptcb->OSTCBDly=0;(4)
if(!(ptcb->OSTCBStat&OS_STAT_SUSPEND)){(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_TIME_NOT_DLY);
}
}else{
OS_EXIT_CRITICAL();
return(OS_TASK_NOT_EXIST);
}
}
注意,用戶(hù)的任務(wù)有可能是通過(guò)暫時(shí)等待信號(hào)量、郵箱或消息隊(duì)列來(lái)延時(shí)自己的(參看第六章)??梢院?jiǎn)單地通過(guò)控制信號(hào)量、郵箱或消息隊(duì)列來(lái)恢復(fù)這樣的任務(wù)。這種情況存在的唯一問(wèn)題是它要求用戶(hù)分配事件控制塊(參看6.00),因此用戶(hù)的應(yīng)用程序會(huì)多占用一些RAM。
5.3系統(tǒng)時(shí)間,OSTimeGet()和 OSTimeSet()
無(wú)論時(shí)鐘節(jié)拍何時(shí)發(fā)生,μC/OS-Ⅱ都會(huì)將一個(gè)32位的計(jì)數(shù)器加1。這個(gè)計(jì)數(shù)器在用戶(hù)調(diào)用OSStart()初始化多任務(wù)和4,294,967,295個(gè)節(jié)拍執(zhí)行完一遍的時(shí)候從0開(kāi)始計(jì)數(shù)。在時(shí)鐘節(jié)拍的頻率等于100Hz的時(shí)候,這個(gè)32位的計(jì)數(shù)器每隔497天就重新開(kāi)始計(jì)數(shù)。用戶(hù)可以通過(guò)調(diào)用OSTimeGet()來(lái)獲得該計(jì)數(shù)器的當(dāng)前值。也可以通過(guò)調(diào)用OSTimeSet()來(lái)改變?cè)撚?jì)數(shù)器的值。OSTimeGet()和OSTimeSet()兩個(gè)函數(shù)的代碼如程序清單L5.4所示。注意,
在訪(fǎng)問(wèn)OSTime的時(shí)候中斷是關(guān)掉的。這是因?yàn)樵诖蠖鄶?shù)8位處理器上增加和拷貝一個(gè)32位的數(shù)都需要數(shù)條指令,這些指令一般都需要一次執(zhí)行完畢,而不能被中斷等因素打斷。
程序清單 L5.4 得到和改變系統(tǒng)時(shí)間
INT32UOSTimeGet(void)
{
INT32Uticks;
OS_ENTER_CRITICAL();
ticks=OSTime;
OS_EXIT_CRITICAL();
return(ticks);
}
voidOSTimeSet(INT32Uticks)
{
OS_ENTER_CRITICAL();
OSTime=ticks;
OS_EXIT_CRITICAL();
}