μC/OS-II的內(nèi)核結(jié)構(gòu)
μC/OS-Ⅱ是怎樣處理臨界段代碼的;
什么是任務(wù),怎樣把用戶的任務(wù)交給μC/OS-Ⅱ;
任務(wù)是怎樣調(diào)度的;
應(yīng)用程序CPU的利用率是多少,μC/OS-Ⅱ是怎樣知道的;
怎樣寫中斷服務(wù)子程序;
什么是時鐘節(jié)拍,μC/OS-Ⅱ是怎樣處理時鐘節(jié)拍的;
μC/OS-Ⅱ是怎樣初始化的,以及
怎樣啟動多任務(wù);
本章還描述以下函數(shù),這些服務(wù)于應(yīng)用程序:
OS_ENTER_CRITICAL() 和 OS_EXIT_CRITICAL(),
OSInit(),
OSSTart(),
OSIntEnter() 和 OSIntExit(),
OSSchedLock() 和 OSSchedUnlock(), 以及
OSVersiON().
3.0 臨界段(CriticalSections)
和其它內(nèi)核一樣,μC/OS-Ⅱ為了處理臨界段代碼需要關(guān)中斷,處理完畢后再開中斷。這使得μC/OS-Ⅱ能夠避免同時有其它任務(wù)或中斷服務(wù)進入臨界段代碼。關(guān)中斷的時間是實時內(nèi)核開發(fā)商應(yīng)提供的最重要的指標之一,因為這個指標影響用戶系統(tǒng)對實時事件的響應(yīng)性。μC/OS-Ⅱ努力使關(guān)中斷時間降至最短,但就使用μC/OS-Ⅱ而言,關(guān)中斷的時間很大程度上取決于微處理器的架構(gòu)以及編譯器所生成的代碼質(zhì)量。
微處理器一般都有關(guān)中斷/開中斷指令,用戶使用的C語言編譯器必須有某種機制能夠在C中直接實現(xiàn)關(guān)中斷/開中斷地操作。某些C編譯器允許在用戶的C源代碼中插入?yún)R編語言的語句。這使得插入微處理器指令來關(guān)中斷/開中斷很容易實現(xiàn)。而有的編譯器把從C語言中關(guān)中斷/開中斷放在語言的擴展部分。μC/OS-Ⅱ定義兩個宏(macros)來關(guān)中斷和開中斷,以便避開不同C編譯器廠商選擇不同的方法來處理關(guān)中斷和開中斷。μC/OS-Ⅱ中的這兩個宏調(diào)用分別是:OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()。因為這兩個宏的定義取決于所用的微處理器,故在文件OS_CPU.H中可以找到相應(yīng)宏定義。每種微處理器都有自己的OS_CPU.H文件。
3.1 任務(wù)
一個任務(wù)通常是一個無限的循環(huán)[L3.1(2)],如程序清單3.1所示。一個任務(wù)看起來像其它C的函數(shù)一樣,有函數(shù)返回類型,有形式參數(shù)變量,但是任務(wù)是絕不會返回的。故返回參數(shù)必須定義成void[L3.1(1)]。
程序清單L3.1任務(wù)是一個無限循環(huán)
voidYourTask(void*pdata)(1)
{
for(;;){(2)
/* 用戶代碼 */
調(diào)用uC/OS-II的某種系統(tǒng)服務(wù):
OSMboxPend();
OSQPend();
OSSemPend();
OSTaskDel(OS_PRIO_SELF);
OSTaskSuspend(OS_PRIO_SELF);
OSTimeDly();
OSTimeDlyHMSM();
/* 用戶代碼 */
}
}
不同的是,當任務(wù)完成以后,任務(wù)可以自我刪除,如清單L3.2所示。注意任務(wù)代碼并非真的刪除了,μC/OS-Ⅱ只是簡單地不再理會這個任務(wù)了,這個任務(wù)的代碼也不會再運行,如果任務(wù)調(diào)用了OSTaskDel(),這個任務(wù)絕不會返回什么。
程序清單 L3.2. 任務(wù)完成后自我刪除
voidYourTask(void*pdata)
{
/* 用戶代碼 */
OSTaskDel(OS_PRIO_SELF);
}
形式參數(shù)變量[L3.1(1)]是由用戶代碼在第一次執(zhí)行的時候帶入的。請注意,該變量的類型是一個指向void的指針。這是為了允許用戶應(yīng)用程序傳遞任何類型的數(shù)據(jù)給任務(wù)。這個指針好比一輛萬能的車子,如果需要的話,可以運載一個變量的地址,或一個結(jié)構(gòu),甚至是一個函數(shù)的地址。也可以建立許多相同的任務(wù),所有任務(wù)都使用同一個函數(shù)(或者說是同一個任務(wù)代碼程序),見第一章的例1。例如,用戶可以將四個串行口安排成每個串行口都是一個單獨的任務(wù),而每個任務(wù)的代碼實際上是相同的。并不需要將代碼復(fù)制四次,用戶可以建立一個任務(wù),向這個任務(wù)傳入一個指向某數(shù)據(jù)結(jié)構(gòu)的指針變量,這個數(shù)據(jù)結(jié)構(gòu)定義串行口的參數(shù)(波特率、I/O口地址、中斷向量號等)。
μC/OS-Ⅱ可以管理多達64個任務(wù),但目前版本的μC/OS-Ⅱ有兩個任務(wù)已經(jīng)被系統(tǒng)占用了。作者保留了優(yōu)先級為0、1、2、3、OS_LOWEST_PRIO-3、OS_LOWEST_PRI0-2,OS_LOWEST_PRI0-1以及OS_LOWEST_PRI0這8個任務(wù)以被將來使用。OS_LOWEST_PRI0是作為定義的常數(shù)在OS_CFG.H文件中用定義常數(shù)語句#defineconstant定義的。因此用戶可以有多達56個應(yīng)用任務(wù)。必須給每個任務(wù)賦以不同的優(yōu)先級,優(yōu)先級可以從0到OS_LOWEST_PR10-2。優(yōu)先級號越低,任務(wù)的優(yōu)先級越高。μC/OS-Ⅱ總是運行進入就緒態(tài)的優(yōu)先級最高的任務(wù)。目前版本的μC/OS-Ⅱ中,任務(wù)的優(yōu)先級號就是任務(wù)編號(ID)。優(yōu)先級號(或任務(wù)的ID號)也被一些內(nèi)核服務(wù)函數(shù)調(diào)用,如改變優(yōu)先級函數(shù)OSTaskChangePrio(),以及任務(wù)刪除函數(shù)OSTaskDel()。
為了使μC/OS-Ⅱ能管理用戶任務(wù),用戶必須在建立一個任務(wù)的時候,將任務(wù)的起始地址與其它參數(shù)一起傳給下面兩個函數(shù)中的一個:OSTastCreat或OSTaskCreatExt()。
OSTaskCreateExt()是OSTaskCreate()的擴展,擴展了一些附加的功能。,這兩個函數(shù)的解釋見第四章,任務(wù)管理。
3.2 任務(wù)狀態(tài)
圖3.1是μC/OS-Ⅱ控制下的任務(wù)狀態(tài)轉(zhuǎn)換圖。在任一給定的時刻,任務(wù)的狀態(tài)一定是在這五種狀態(tài)之一。
睡眠態(tài)(DORMANT)指任務(wù)駐留在程序空間之中,還沒有交給μC/OS-Ⅱ管理,(見程序清單L3.1或L3.2)。把任務(wù)交給μC/OS-Ⅱ是通過調(diào)用下述兩個函數(shù)之一:
OSTaskCreate()或OSTaskCreateExt()。當任務(wù)一旦建立,這個任務(wù)就進入就緒態(tài)準備運行。任務(wù)的建立可以是在多任務(wù)運行開始之前,也可以是動態(tài)地被一個運行著的任務(wù)建立。如果一個任務(wù)是被另一個任務(wù)建立的,而這個任務(wù)的優(yōu)先級高于建立它的那個任務(wù),則這個剛剛建立的任務(wù)將立即得到CPU的控制權(quán)。一個任務(wù)可以通過調(diào)用OSTaskDel()返回到睡眠態(tài),或通過調(diào)用該函數(shù)讓另一個任務(wù)進入睡眠態(tài)。
調(diào)用OSStart()可以啟動多任務(wù)。OSStart()函數(shù)運行進入就緒態(tài)的優(yōu)先級最高的任務(wù)。就緒的任務(wù)只有當所有優(yōu)先級高于這個任務(wù)的任務(wù)轉(zhuǎn)為等待狀態(tài),或者是被刪除了,才能進入運行態(tài)。
圖3.1任務(wù)的狀態(tài)
正在運行的任務(wù)可以通過調(diào)用兩個函數(shù)之一將自身延遲一段時間,這兩個函數(shù)是OSTimeDly()或OSTimeDlyHMSM()。這個任務(wù)于是進入等待狀態(tài),等待這段時間過去,下一個優(yōu)先級最高的、并進入了就緒態(tài)的任務(wù)立刻被賦予了CPU的控制權(quán)。等待的時間過去以后,系統(tǒng)服務(wù)函數(shù)OSTimeTick()使延遲了的任務(wù)進入就緒態(tài)(見3.10節(jié),時鐘節(jié)拍)。
正在運行的任務(wù)期待某一事件的發(fā)生時也要等待,手段是調(diào)用以下3個函數(shù)之一:
OSSemPend(),OSMboxPend(),或OSQPend()。調(diào)用后任務(wù)進入了等待狀態(tài)(WAITING)。當任務(wù)因等待事件被掛起(Pend),下一個優(yōu)先級最高的任務(wù)立即得到了CPU的控制權(quán)。當事件發(fā)生了,被掛起的任務(wù)進入就緒態(tài)。事件發(fā)生的報告可能來自另一個任務(wù),也可能來自中斷服務(wù)子程序。
正在運行的任務(wù)是可以被中斷的,除非該任務(wù)將中斷關(guān)了,或者μC/OS-Ⅱ?qū)⒅袛嚓P(guān)了。被中斷了的任務(wù)就進入了中斷服務(wù)態(tài)(ISR)。響應(yīng)中斷時,正在執(zhí)行的任務(wù)被掛起,中斷服務(wù)子程序控制了CPU的使用權(quán)。中斷服務(wù)子程序可能會報告一個或多個事件的發(fā)生,而使一個或多個任務(wù)進入就緒態(tài)。在這種情況下,從中斷服務(wù)子程序返回之前,μC/OS-Ⅱ要判定,被中斷的任務(wù)是否還是就緒態(tài)任務(wù)中優(yōu)先級最高的。如果中斷服務(wù)子程序使一個優(yōu)先級更高的任務(wù)進入了就緒態(tài),則新進入就緒態(tài)的這個優(yōu)先級更高的任務(wù)將得以運行,否則原來被中斷了的任務(wù)才能繼續(xù)運行。當所有的任務(wù)都在等待事件發(fā)生或等待延遲時間結(jié)束,μC/OS-Ⅱ執(zhí)行空閑任務(wù)(idletask),執(zhí)行OSTaskIdle()函數(shù)。
3.3 任務(wù)控制塊(TaskControlBlocks,OS _TCBs)
一旦任務(wù)建立了,任務(wù)控制塊OS _TCBs將被賦值(程序清單3.3)。任務(wù)控制塊是一個數(shù)據(jù)結(jié)構(gòu),當任務(wù)的CPU使用權(quán)被剝奪時,μC/OS-Ⅱ用它來保存該任務(wù)的狀態(tài)。當任務(wù)重新得到CPU使用權(quán)時,任務(wù)控制塊能確保任務(wù)從當時被中斷的那一點絲毫不差地繼續(xù)執(zhí)行。OS _TCBs全部駐留在RAM中。讀者將會注意到筆者在組織這個數(shù)據(jù)結(jié)構(gòu)時,考慮到了各成員的邏輯分組。任務(wù)建立的時候,OS _TCBs就被初始化了(見第四章任務(wù)管理)。
程序清單 L3.3μC/OS-II任務(wù)控制塊.
typedefstructos_tcb{
OS_STK*OSTCBStkPtr;
#ifOS_TASK_CREATE_EXT_EN
void*OSTCBExtPtr;
OS_STK*OSTCBStkBottom;
INT32UOSTCBStkSize;
INT16UOSTCBOpt;
INT16UOSTCBId;
#endif
structos_tcb*OSTCBNext;
structos_tcb*OSTCBPrev;
#if(OS_Q_EN&&(OS_MAX_QS>=2))||OS_MBOX_EN||OS_SEM_EN
OS_EVENT*OSTCBEventPtr;
#endif
#if(OS_Q_EN&&(OS_MAX_QS>=2))||OS_MBOX_EN
void*OSTCBMsg;
#endif
INT16UOSTCBDly;
INT8UOSTCBStat;
INT8UOSTCBPrio;
INT8UOSTCBX;
INT8UOSTCBY;
INT8UOSTCBBitX;
INT8UOSTCBBitY;
#ifOS_TASK_DEL_EN
BOOLEANOSTCBDelReq;
#endif
}OS_TCB;
.OSTCBStkPtr 是指向當前任務(wù)棧頂?shù)闹羔槨?mu;C/OS-Ⅱ允許每個任務(wù)有自己的棧,尤為重
要的是,每個任務(wù)的棧的容量可以是任意的。有些商業(yè)內(nèi)核要求所有任務(wù)棧的容量都一
樣,除非用戶寫一個復(fù)雜的接口函數(shù)來改變之。這種限制浪費了RAM,當各任務(wù)需要的棧
空間不同時,也得按任務(wù)中預(yù)期棧容量需求最多的來分配??臻g。OSTCBStkPtr是OS_TCB
數(shù)據(jù)結(jié)構(gòu)中唯一的一個能用匯編語言來處置的變量(在任務(wù)切換段的代碼Context-
switchingcode之中,)把OSTCBStkPtr放在數(shù)據(jù)結(jié)構(gòu)的最前面,使得從匯編語言中處理
這個變量時較為容易。
.OSTCBExtPtr 指向用戶定義的任務(wù)控制塊擴展。用戶可以擴展任務(wù)控制塊而不必修改μ
C/OS-Ⅱ的源代碼。.OSTCBExtPtr只在函數(shù)OstaskCreateExt()中使用,故使用時要將
OS_TASK_CREAT_EN設(shè)為1,以允許建立任務(wù)函數(shù)的擴展。例如用戶可以建立一個數(shù)據(jù)結(jié)
構(gòu),這個數(shù)據(jù)結(jié)構(gòu)包含每個任務(wù)的名字,或跟蹤某個任務(wù)的執(zhí)行時間,或者跟蹤切換到某
個任務(wù)的次數(shù)(見例3)。注意,筆者將這個擴展指針變量放在緊跟著堆棧指針的位置,
為的是當用戶需要在匯編語言中處理這個變量時,從數(shù)據(jù)結(jié)構(gòu)的頭上算偏移量比較方便。
.OSTCBStkBottom 是指向任務(wù)棧底的指針。如果微處理器的棧指針是遞減的,即棧存儲器
從高地址向低地址方向分配,則OSTCBStkBottom指向任務(wù)使用的??臻g的最低地址。類似
地,如果微處理器的棧是從低地址向高地址遞增型的,則OSTCBStkBottom指向任務(wù)可以使
用的??臻g的最高地址。函數(shù)OSTaskStkChk()要用到變量OSTCBStkBottom,在運行中檢驗
??臻g的使用情況。用戶可以用它來確定任務(wù)實際需要的??臻g。這個功能只有當用戶在
任務(wù)建立時允許使用OSTaskCreateExt()函數(shù)時才能實現(xiàn)。這就要求用戶將
OS_TASK_CREATE_EXT_EN設(shè)為1,以便允許該功能。
.OSTCBStkSize 存有棧中可容納的指針元數(shù)目而不是用字節(jié)(Byte)表示的棧容量總數(shù)。
也就是說,如果棧中可以保存1,000個入口地址,每個地址寬度是32位的,則實際棧容量
是4,000字節(jié)。同樣是1,000個入口地址,如果每個地址寬度是16位的,則總棧容量只有
2,000字節(jié)。在函數(shù)OSStakChk()中要調(diào)用OSTCBStkSize。同理,若使用該函數(shù)的話,要將
OS_TASK_CREAT_EXT_EN設(shè)為1。
.OSTCBOpt 把“選擇項”傳給OSTaskCreateExt(),只有在用戶將OS_TASK_CREATE_EXT_EN
設(shè)為1時,這個變量才有效。μC/OS-Ⅱ目前只支持3個選擇項(見uCOS_II.H):
OS_TASK_OTP_STK_CHK,OS_TASK_OPT_STK_CLR和OS_TASK_OPT_SAVE_FP。
OS_TASK_OTP_STK_CHK用于告知TaskCreateExt(),在任務(wù)建立的時候任務(wù)棧檢驗功能得
到了允許。OS_TASK_OPT_STK_CLR表示任務(wù)建立的時候任務(wù)棧要清零。只有在用戶需要有
棧檢驗功能時,才需要將棧清零。如果不定義OS_TASK_OPT_STK_CLR,而后又建立、刪除[!--empirenews.page--]
了任務(wù),棧檢驗功能報告的棧使用情況將是錯誤的。如果任務(wù)一旦建立就決不會被刪除,
而用戶初始化時,已將RAM清過零,則OS_TASK_OPT_STK_CLR不需要再定義,這可以節(jié)約程
序執(zhí)行時間。傳遞了OS_TASK_OPT_STK_CLR將增加TaskCreateExt()函數(shù)的執(zhí)行時間,因為
要將??臻g清零。棧容量越大,清零花的時間越長。最后一個選擇項
OS_TASK_OPT_SAVE_FP通知TaskCreateExt(),任務(wù)要做浮點運算。如果微處理器有硬件的
浮點協(xié)處理器,則所建立的任務(wù)在做任務(wù)調(diào)度切換時,浮點寄存器的內(nèi)容要保存。
.OSTCBId用于存儲任務(wù)的識別碼。這個變量現(xiàn)在沒有使用,留給將來擴展用。
.OSTCBNext 和.OSTCBPrev 用于任務(wù)控制塊OS_TCBs的雙重鏈接,該鏈表在時鐘節(jié)拍函數(shù)
OSTimeTick()中使用,用于刷新各個任務(wù)的任務(wù)延遲變量.OSTCBDly,每個任務(wù)的任務(wù)控制
塊OS_TCB在任務(wù)建立的時候被鏈接到鏈表中,在任務(wù)刪除的時候從鏈表中被刪除。雙重連
接的鏈表使得任一成員都能被快速插入或刪除。
.OSTCBEventPtr是指向事件控制塊的指針,后面的章節(jié)中會有所描述(見第6章任務(wù)間通
訊與同步)。
.OSTCBMsg是指向傳給任務(wù)的消息的指針。用法將在后面的章節(jié)中提到(見第6章任務(wù)間通
訊與同步)。
.OSTCBDly 當需要把任務(wù)延時若干時鐘節(jié)拍時要用到這個變量,或者需要把任務(wù)掛起一段
時間以等待某事件的發(fā)生,這種等待是有超時限制的。在這種情況下,這個變量保存的是
任務(wù)允許等待事件發(fā)生的最多時鐘節(jié)拍數(shù)。如果這個變量為0,表示任務(wù)不延時,或者表
示等待事件發(fā)生的時間沒有限制。
.OSTCBStat是任務(wù)的狀態(tài)字。當.OSTCBStat為0,任務(wù)進入就緒態(tài)??梢越o.OSTCBStat賦
其它的值,在文件uCOS_II.H中有關(guān)于這個值的描述。
.OSTCBPrio是任務(wù)優(yōu)先級。高優(yōu)先級任務(wù)的.OSTCBPrio值小。也就是說,這個值越小,任
務(wù)的優(yōu)先級越高。
.OSTCBX,.OSTCBY,.OSTCBBitX和.OSTCBBitY 用于加速任務(wù)進入就緒態(tài)的過程或進入等
待事件發(fā)生狀態(tài)的過程(避免在運行中去計算這些值)。這些值是在任務(wù)建立時算好的,
或者是在改變?nèi)蝿?wù)優(yōu)先級時算出的。這些值的算法見程序清單L3.4。
程序清單 L3.4 任務(wù)控制塊OS_TCB中幾個成員的算法
OSTCBY=priority>>3;
OSTCBBitY=OSMapTbl[priority>>3];
OSTCBX=priority&0x07;
OSTCBBitX=OSMapTbl[priority&0x07];
.OSTCBDelReq是一個布爾量,用于表示該任務(wù)是否需要刪除,用法將在后面的章節(jié)中描述
(見第4章任務(wù)管理)
應(yīng)用程序中可以有的最多任務(wù)數(shù)(OS_MAX_TASKS)是在文件OS_CFG.H中定義的。這個
最多任務(wù)數(shù)也是μC/OS-Ⅱ分配給用戶程序的最多任務(wù)控制塊OS_TCBs的數(shù)目。將
OS_MAX_TASKS的數(shù)目設(shè)置為用戶應(yīng)用程序?qū)嶋H需要的任務(wù)數(shù)可以減小RAM的需求量。所有
的任務(wù)控制塊OS_TCBs都是放在任務(wù)控制塊列表數(shù)組OSTCBTbl[]中的。請注意,μC/OS-Ⅱ
分配給系統(tǒng)任務(wù)OS_N_SYS_TASKS若干個任務(wù)控制塊,見文件μC/OS-Ⅱ.H,供其內(nèi)部使
用。目前,一個用于空閑任務(wù),另一個用于任務(wù)統(tǒng)計(如果OS_TASK_STAT_EN是設(shè)為
1的)。在μC/OS-Ⅱ初始化的時候,如圖3.2所示,所有任務(wù)控制塊OS_TCBs被鏈接成單
向空任務(wù)鏈表。當任務(wù)一旦建立,空任務(wù)控制塊指針OSTCBFreeList指向的任務(wù)控制塊便
賦給了該任務(wù),然后OSTCBFreeList的值調(diào)整為指向下鏈表中下一個空的任務(wù)控制塊。一
旦任務(wù)被刪除,任務(wù)控制塊就還給空任務(wù)鏈表。
圖3.2空任務(wù)列表
3.4 就緒表(ReadyList)
每個任務(wù)被賦予不同的優(yōu)先級等級,從0級到最低優(yōu)先級OS_LOWEST_PR1O,包括0和S_LOWEST_PR1O在內(nèi)(見文件OS_CFG.H)。當μC/OS-Ⅱ初始化的時候,最低優(yōu)先級OS_LOWEST_PR1O總是被賦給空閑任務(wù)idletask。注意,最多任務(wù)數(shù)目OS_MAX_TASKS和最低優(yōu)先級數(shù)是沒有關(guān)系的。用戶應(yīng)用程序可以只有10個任務(wù),而仍然可以有32個優(yōu)先級的級別(如果用戶將最低優(yōu)先級數(shù)設(shè)為31的話)。
每個任務(wù)的就緒態(tài)標志都放入就緒表中的,就緒表中有兩個變量OSRedyGrp和OSRdyTbl[]。在OSRdyGrp中,任務(wù)按優(yōu)先級分組,8個任務(wù)為一組。OSRdyGrp中的每一位表示8組任務(wù)中每一組中是否有進入就緒態(tài)的任務(wù)。任務(wù)進入就緒態(tài)時,就緒表OSRdyTbl[]中的相應(yīng)元素的相應(yīng)位也置位。就緒表OSRdyTbl[]數(shù)組的大小取決于OS_LOWEST_PR1O(見文件OS_CFG.H)。當用戶的應(yīng)用程序中任務(wù)數(shù)目比較少時,減少OS_LOWEST_PR1O的值可以降低μC/OS-Ⅱ?qū)AM(數(shù)據(jù)空間)的需求量。
為確定下次該哪個優(yōu)先級的任務(wù)運行了,內(nèi)核調(diào)度器總是將OS_LOWEST_PR1O在就緒表中相應(yīng)字節(jié)的相應(yīng)位置1。OSRdyGrp和OSRdyTbl[]之間的關(guān)系見圖3.3,是按以下規(guī)則給出的:
當OSRdyTbl[0]中的任何一位是1時,OSRdyGrp的第0位置1,
當OSRdyTbl[1]中的任何一位是1時,OSRdyGrp的第1位置1,
當OSRdyTbl[2]中的任何一位是1時,OSRdyGrp的第2位置1,
當OSRdyTbl[3]中的任何一位是1時,OSRdyGrp的第3位置1,
當OSRdyTbl[4]中的任何一位是1時,OSRdyGrp的第4位置1,
當OSRdyTbl[5]中的任何一位是1時,OSRdyGrp的第5位置1,
當OSRdyTbl[6]中的任何一位是1時,OSRdyGrp的第6位置1,
當OSRdyTbl[7]中的任何一位是1時,OSRdyGrp的第7位置1,
程序清單3.5中的代碼用于將任務(wù)放入就緒表。Prio是任務(wù)的優(yōu)先級。
程序清單L3.5使任務(wù)進入就緒態(tài)
OSRdyGrp|=OSMapTbl[prio>>3];
OSRdyTbl[prio>>3]|=OSMapTbl[prio&0x07];
表T3.1OSMapTbl[]的值
讀者可以看出,任務(wù)優(yōu)先級的低三位用于確定任務(wù)在總就緒表OSRdyTbl[]中的所在位。接下去的三位用于確定是在OSRdyTbl[]數(shù)組的第幾個元素。OSMapTbl[]是在ROM中的(見文件OS_CORE.C)屏蔽字,用于限制OSRdyTbl[]數(shù)組的元素下標在0到7之間,見表3.1
圖3.3μC/OS-Ⅱ就緒表
如果一個任務(wù)被刪除了,則用程序清單3.6中的代碼做求反處理。
程序清單L3.6從就緒表中刪除一個任務(wù)
if((OSRdyTbl[prio>>3]&=~OSMapTbl[prio&0x07])==0)
OSRdyGrp&=~OSMapTbl[prio>>3];
以上代碼將就緒任務(wù)表數(shù)組OSRdyTbl[]中相應(yīng)元素的相應(yīng)位清零,而對于OSRdyGrp,
只有當被刪除任務(wù)所在任務(wù)組中全組任務(wù)一個都沒有進入就緒態(tài)時,才將相應(yīng)位清零。也
就是說OSRdyTbl[prio>>3]所有的位都是零時,OSRdyGrp的相應(yīng)位才清零。為了找到那個
進入就緒態(tài)的優(yōu)先級最高的任務(wù),并不需要從OSRdyTbl[0]開始掃描整個就緒任務(wù)表,只
需要查另外一張表,即優(yōu)先級判定表OSUnMapTbl([256])(見文件 OS_CORE.C)。OSRdyTbl[]
中每個字節(jié)的8位代表這一組的8個任務(wù)哪些進入就緒態(tài)了,低位的優(yōu)先級高于高位。利用
這個字節(jié)為下標來查OSUnMapTbl這張表,返回的字節(jié)就是該組任務(wù)中就緒態(tài)任務(wù)中優(yōu)先級
最高的那個任務(wù)所在的位置。這個返回值在0到7之間。確定進入就緒態(tài)的優(yōu)先級最高的任
務(wù)是用以下代碼完成的,如程序清單L3.7所示。
程序清單 L3.7 找出進入就緒態(tài)的優(yōu)先級最高的任務(wù)
y=OSUnMapTbl[OSRdyGrp];
x=OSUnMapTbl[OSRdyTbl[y]];
prio=(y<<3)+x;
例如,如果OSRdyGrp的值為二進制01101000,查OSUnMapTbl[OSRdyGrp]得到的值是
3,它相應(yīng)于OSRdyGrp中的第3位bit3,這里假設(shè)最右邊的一位是第0位bit0。類似地,
如果OSRdyTbl[3]的值是二進制11100100,則OSUnMapTbl[OSRdyTbc[3]]的值是2,即第2
位。于是任務(wù)的優(yōu)先級Prio就等于26(3*8+2)。利用這個優(yōu)先級的值。查任務(wù)控制塊優(yōu)
先級表OSTCBPrioTbl[],得到指向相應(yīng)任務(wù)的任務(wù)控制塊OS_TCB的工作就完成了。
3.5 任務(wù)調(diào)度(TaskScheduling)
μC/OS-Ⅱ總是運行進入就緒態(tài)任務(wù)中優(yōu)先級最高的那一個。確定哪個任務(wù)優(yōu)先級最
高,下面該哪個任務(wù)運行了的工作是由調(diào)度器(Scheduler)完成的。任務(wù)級的調(diào)度是由函
數(shù)OSSched()完成的。中斷級的調(diào)度是由另一個函數(shù)OSIntExt()完成的,這個函數(shù)將在以
后描述。OSSched()的代碼如程序清單L3.8所示。
程序清單L3.8任務(wù)調(diào)度器(theTaskScheduler)
voidOSSched(void)
{
INT8Uy;
OS_ENTER_CRITICAL();
if((OSLockNesting|OSIntNesting)==0){(1)
y=OSUnMapTbl[OSRdyGrp];(2)
OSPrioHighRdy=(INT8U)((y<<3)+OSUnMapTbl[OSRdyTbl[y]]);(2)
if(OSPrioHighRdy!=OSPrioCur){(3)
OSTCBHighRdy=OSTCBPrioTbl[OSPrioHighRdy];(4)
OSCtxSwCtr++;(5)
OS_TASK_SW();(6)
}
}
OS_EXIT_CRITICAL();
}
μC/OS-Ⅱ任務(wù)調(diào)度所花的時間是常數(shù),與應(yīng)用程序中建立的任務(wù)數(shù)無關(guān)。如程序清單
中[L3.8(1)]條件語句的條件不滿足,任務(wù)調(diào)度函數(shù)OSSched()將退出,不做任務(wù)調(diào)度。這
個條件是:如果在中斷服務(wù)子程序中調(diào)用OSSched(),此時中斷嵌套層數(shù)
OSIntNesting>0,或者由于用戶至少調(diào)用了一次給任務(wù)調(diào)度上鎖函數(shù)OSSchedLock(),使
OSLockNesting>0。如果不是在中斷服務(wù)子程序調(diào)用OSSched(),并且任務(wù)調(diào)度是允許的,
即沒有上鎖,則任務(wù)調(diào)度函數(shù)將找出那個進入就緒態(tài)且優(yōu)先級最高的任務(wù)[L3.8(2)],進入
就緒態(tài)的任務(wù)在就緒任務(wù)表中有相應(yīng)的位置位。一旦找到那個優(yōu)先級最高的任務(wù),
OSSched()檢驗這個優(yōu)先級最高的任務(wù)是不是當前正在運行的任務(wù),以此來避免不必要的任
務(wù)調(diào)度[L3.8(3)]。注意,在μC/OS中曾經(jīng)是先得到OSTCBHighRdy然后和OSTCBCur做比
較。因為這個比較是兩個指針型變量的比較,在8位和一些16位微處理器中這種比較相對
較慢。而在μC/OS-Ⅱ中是兩個整數(shù)的比較。并且,除非用戶實際需要做任務(wù)切換,在查任
務(wù)控制塊優(yōu)先級表OSTCBPrioTbl[]時,不需要用指針變量來查OSTCBHighRdy。綜合這兩項
改進,即用整數(shù)比較代替指針的比較和當需要任務(wù)切換時再查表,使得μC/OS-Ⅱ比μC/OS
在8位和一些16位微處理器上要更快一些。
為實現(xiàn)任務(wù)切換,OSTCBHighRdy必須指向優(yōu)先級最高的那個任務(wù)控制塊OS_TCB,這是
通過將以O(shè)SPrioHighRdy為下標的OSTCBPrioTbl[]數(shù)組中的那個元素賦給OSTCBHighRdy來
實現(xiàn)的[L3.8(4)]。接著,統(tǒng)計計數(shù)器OSCtxSwCtr加1,以跟蹤任務(wù)切換次數(shù)[L3.8(5)]。
最后宏調(diào)用OS_TASK_SW()來完成實際上的任務(wù)切換[L3.8(6)]。
任務(wù)切換很簡單,由以下兩步完成,將被掛起任務(wù)的微處理器寄存器推入堆棧,然后
將較高優(yōu)先級的任務(wù)的寄存器值從棧中恢復(fù)到寄存器中。在μC/OS-Ⅱ中,就緒任務(wù)的棧結(jié)
構(gòu)總是看起來跟剛剛發(fā)生過中斷一樣,所有微處理器的寄存器都保存在棧中。換句話說,
μC/OS-Ⅱ運行就緒態(tài)的任務(wù)所要做的一切,只是恢復(fù)所有的CPU寄存器并運行中斷返回指
令。為了做任務(wù)切換,運行OS_TASK_SW(),人為模仿了一次中斷。多數(shù)微處理器有軟中斷
指令或者陷阱指令TRAP來實現(xiàn)上述操作。中斷服務(wù)子程序或陷阱處理(Traphardler),
也稱作事故處理(exceptionhandler),必須提供中斷向量給匯編語言函數(shù)OSCtxSw()。
OSCtxSw()除了需要OS_TCBHighRdy指向即將被掛起的任務(wù),還需要讓當前任務(wù)控制塊
OSTCBCur指向即將被掛起的任務(wù),參見第8章,移植μC/OS-Ⅱ,有關(guān)于OSCtxSw()的更詳[!--empirenews.page--]
盡的解釋。
OSSched()的所有代碼都屬臨界段代碼。在尋找進入就緒態(tài)的優(yōu)先級最高的任務(wù)過程
中,為防止中斷服務(wù)子程序把一個或幾個任務(wù)的就緒位置位,中斷是被關(guān)掉的。為縮短切
換時間,OSSched()全部代碼都可以用匯編語言寫。為增加可讀性,可移植性和將匯編語言
代碼最少化,OSSched()是用C寫的。
3.6 給調(diào)度器上鎖和開鎖(LockingandUnLockingtheScheduler)
給調(diào)度器上鎖函數(shù)OSSchedlock()(程序清單L3.9)用于禁止任務(wù)調(diào)度,直到任務(wù)完
成后調(diào)用給調(diào)度器開鎖函數(shù)OSSchedUnlock()為止,(程序清單L3.10)。調(diào)用
OSSchedlock()的任務(wù)保持對CPU的控制權(quán),盡管有個優(yōu)先級更高的任務(wù)進入了就緒態(tài)。然
而,此時中斷是可以被識別的,中斷服務(wù)也能得到(假設(shè)中斷是開著的)。OSSchedlock()
和OSSchedUnlock()必須成對使用。變量OSLockNesting跟蹤OSSchedLock()函數(shù)被調(diào)用的
次數(shù),以允許嵌套的函數(shù)包含臨界段代碼,這段代碼其它任務(wù)不得干預(yù)。μC/OS-Ⅱ允許嵌
套深度達255層。當OSLockNesting等于零時,調(diào)度重新得到允許。函數(shù)OSSchedLock()和
OSSchedUnlock()的使用要非常謹慎,因為它們影響μC/OS-Ⅱ?qū)θ蝿?wù)的正常管理。
當OSLockNesting減到零的時候,OSSchedUnlock()調(diào)用OSSched[L3.10(2)]。
OSSchedUnlock()是被某任務(wù)調(diào)用的,在調(diào)度器上鎖的期間,可能有什么事件發(fā)生了并使一
個更高優(yōu)先級的任務(wù)進入就緒態(tài)。
調(diào)用OSSchedLock()以后,用戶的應(yīng)用程序不得使用任何能將現(xiàn)行任務(wù)掛起的系統(tǒng)調(diào)用。也就是說,用戶程序不得調(diào)用OSMboxPend()、OSQPend()、OSSemPend()、OSTaskSuspend
(OS_PR1O_SELF)、OSTimeDly()或OSTimeDlyHMSM(),直到OSLockNesting回零為止。因為調(diào)度器上了鎖,用戶就鎖住了系統(tǒng),任何其它任務(wù)都不能運行。
當?shù)蛢?yōu)先級的任務(wù)要發(fā)消息給多任務(wù)的郵箱、消息隊列、信號量時(見第6章任務(wù)間通訊和同步),用戶不希望高優(yōu)先級的任務(wù)在郵箱、隊列和信號量沒有得到消息之前就取得了CPU的控制權(quán),此時,用戶可以使用禁止調(diào)度器函數(shù)。
程序清單 L3.9 給調(diào)度器上鎖
voidOSSchedLock(void)
{
if(OSRunning==TRUE){
OS_ENTER_CRITICAL();
OSLockNesting++;
OS_EXIT_CRITICAL();
}
}
程序清單L3.10給調(diào)度器開鎖.
voidOSSchedUnlock(void)
{
if(OSRunning==TRUE){
OS_ENTER_CRITICAL();
if(OSLockNesting>0){
OSLockNesting--;
if((OSLockNesting|OSIntNesting)==0){(1)
OS_EXIT_CRITICAL();
OSSched();(2)
}else{
OS_EXIT_CRITICAL();
}
}else{
OS_EXIT_CRITICAL();
}
}
}
3.7 空閑任務(wù)(IdleTask)
μC/OS-Ⅱ總是建立一個空閑任務(wù),這個任務(wù)在沒有其它任務(wù)進入就緒態(tài)時投入運行。這個空閑任務(wù)[OSTaskIdle()]永遠設(shè)為最低優(yōu)先級,即OS_LOWEST_PRI0。空閑任務(wù)OSTaskIdle()什么也不做,只是在不停地給一個32位的名叫OSIdleCtr的計數(shù)器加1,統(tǒng)計任務(wù)(見3.08節(jié),統(tǒng)計任務(wù))使用這個計數(shù)器以確定現(xiàn)行應(yīng)用軟件實際消耗的CPU時間。程序清單L3.11是空閑任務(wù)的代碼。在計數(shù)器加1前后,中斷是先關(guān)掉再開啟的,因為8位以及大多數(shù)16位微處理器的32位加1需要多條指令,要防止高優(yōu)先級的任務(wù)或中斷服務(wù)子程序從中打入??臻e任務(wù)不可能被應(yīng)用軟件刪除。
程序清單L3.11μC/OS-Ⅱ的空閑任務(wù).
voidOSTaskIdle(void*pdata)
{
pdata=pdata;
for(;;){
OS_ENTER_CRITICAL();
OSIdleCtr++;
OS_EXIT_CRITICAL();
}
}
3.8 統(tǒng)計任務(wù)
μC/OS-Ⅱ有一個提供運行時間統(tǒng)計的任務(wù)。這個任務(wù)叫做OSTaskStat(),如果用戶將系統(tǒng)定義常數(shù)OS_TASK_STAT_EN(見文件OS_CFG.H)設(shè)為1,這個任務(wù)就會建立。一旦得到了允許,OSTaskStat()每秒鐘運行一次(見文件OS_CORE.C),計算當前的CPU利用率。換句話說,OSTaskStat()告訴用戶應(yīng)用程序使用了多少CPU時間,用百分比表示,這個值放在一個有符號8位整數(shù)OSCPUsage中,精讀度是1個百分點。
如果用戶應(yīng)用程序打算使用統(tǒng)計任務(wù),用戶必須在初始化時建立一個唯一的任務(wù),在這個任務(wù)中調(diào)用OSStatInit()(見文件OS_CORE.C)。換句話說,在調(diào)用系統(tǒng)啟動函數(shù)OSStart()之前,用戶初始代碼必須先建立一個任務(wù),在這個任務(wù)中調(diào)用系統(tǒng)統(tǒng)計初始化函數(shù)OSStatInit(),然后再建立應(yīng)用程序中的其它任務(wù)。程序清單L3.12是統(tǒng)計任務(wù)的示意性代碼。
程序清單L3.12初始化統(tǒng)計任務(wù).
voidmain(void)
{
OSInit();/* 初始化uC/OS-II(1)*/
/* 安裝uC/OS-II的任務(wù)切換向量 */
/* 創(chuàng)建用戶起始任務(wù)(為了方便討論,這里以TaskStart()作為起始任務(wù))(2)*/
OSStart();/* 開始多任務(wù)調(diào)度 (3)*/
}
voidTaskStart(void*pdata)
{
/* 安裝并啟動uC/OS-II的時鐘節(jié)拍 (4)*/
OSStatInit();/* 初始化統(tǒng)計任務(wù) (5)*/
/* 創(chuàng)建用戶應(yīng)用程序任務(wù) */
for(;;){
/* 這里是TaskStart()的代碼!*/
}
}
因為用戶的應(yīng)用程序必須先建立一個起始任務(wù)[TaskStart()],當主程序main()調(diào)用系統(tǒng)啟動函數(shù)OSStcnt()的時候,μC/OS-Ⅱ只有3個要管理的任務(wù):TaskStart()、OSTaskIdle()和OSTaskStat()。請注意,任務(wù)TaskStart()的名稱是無所謂的,叫什么名字都可以。因為μC/OS-Ⅱ已經(jīng)將空閑任務(wù)的優(yōu)先級設(shè)為最低,即OS_LOWEST_PR10,統(tǒng)計任務(wù)的優(yōu)先級設(shè)為次低,OS_LOWEST_PR10-1。啟動任務(wù)TaskStart()總是優(yōu)先級最高的任務(wù)。
圖F3.4解釋初始化統(tǒng)計任務(wù)時的流程。用戶必須首先調(diào)用的是μC/OS-Ⅱ中的系統(tǒng)初始化函數(shù)OSInit(),該函數(shù)初始化μC/OS-Ⅱ[圖F3.4(2)]。有的處理器(例如Motorola的MC68HC11),不需要“設(shè)置”中斷向量,中斷向量已經(jīng)在ROM中有了。用戶必須調(diào)用OSTaskCreat()或者OSTaskCreatExt()以建立TaskStart()[圖F3.4(3)]。進入多任務(wù)的條件準備好了以后,調(diào)用系統(tǒng)啟動函數(shù)OSStart()。這個函數(shù)將使 TaskStart()開始執(zhí)行,因為TaskStart()是優(yōu)先級最高的任務(wù)[圖F3.4(4)]]。[!--empirenews.page--]
圖F3.4統(tǒng)計任務(wù)的初始化
TaskStart()負責初始化和啟動時鐘節(jié)拍[圖F3.4(5)]。在這里啟動時鐘節(jié)拍是必要的,因為用戶不會希望在多任務(wù)還沒有開始時就接收到時鐘節(jié)拍中斷。接下去TaskStart()調(diào)用統(tǒng)計初始化函數(shù)OSStatInit()[圖F3.4(6)]。統(tǒng)計初始化函數(shù)OSStatInit()決定在沒有其它應(yīng)用任務(wù)運行時,空閑計數(shù)器(OSIdleCtr)的計數(shù)有多快。奔騰II微處理器以333MHz運行時,加1操作可以使該計數(shù)器的值達到每秒15,000,000次。OSIdleCtr的值離32位計數(shù)器的溢出極限值4,294,967,296還差得遠。微處理器越來越快,用戶要注意這里可能會是將來的一個潛在問題。
系統(tǒng)統(tǒng)計初始化任務(wù)函數(shù)OSStatInit()調(diào)用延遲函數(shù)OSTimeDly()將自身延時2個時鐘節(jié)拍以停止自身的運行[圖F3.4(7)]。這是為了使OSStatInit()與時鐘節(jié)拍同步。μC/OS-Ⅱ然后選下一個優(yōu)先級最高的進入就緒態(tài)的任務(wù)運行,這恰好是統(tǒng)計任務(wù)OSTaskStat()。
讀者會在后面讀到OSTaskStat()的代碼,但粗看一下,OSTaskStat()所要做的第一件事就是查看統(tǒng)計任務(wù)就緒標志是否為“假”,如果是的話,也要延時兩個時鐘節(jié)拍[圖F3.4(8)]。一定會是這樣,因為標志OSStatRdy已被OSInit()函數(shù)初始化為“假”,所以
實際上DSTaskStat也將自己推入休眠態(tài)(Sleep)兩個時鐘節(jié)拍[圖F3.4(9)]。于是任務(wù)切換到空閑任務(wù),OSTaskIdle()開始運行,這是唯一一個就緒態(tài)任務(wù)了。CPU處在空閑任務(wù)OSTaskIdle中,直到TaskStart()的延遲兩個時鐘節(jié)拍完成[圖3.4(10)]。兩個時鐘節(jié)拍之后,TaskStart()恢復(fù)運行[圖F3.4(11)]。 在執(zhí)行OSStartInit()時,空閑計數(shù)器OSIdleCtr被清零[圖F3.4(12)]。然后,OSStatInit()將自身延時整整一秒[圖F3.4(13)]。因為沒有其它進入就緒態(tài)的任務(wù),OSTaskIdle()又獲得了CPU的控制權(quán)[圖F3.4(14)]。一秒鐘以后,TaskStart()繼續(xù)運行,還是在OSStatInit()中,空閑計數(shù)器將1秒鐘內(nèi)計數(shù)的值存入空閑計數(shù)器最大值OSIdleCtrMax中[圖F3.4(15)]。
OSStarInit()將統(tǒng)計任務(wù)就緒標志OSStatRdy設(shè)為“真”[圖F3.4(16)],以此來允許兩個時鐘節(jié)拍以后OSTaskStat()開始計算CPU的利用率。
統(tǒng)計任務(wù)的初始化函數(shù)OSStatInit()的代碼如程序清單L3.13所示。
程序清單L3.13統(tǒng)計任務(wù)的初始化.
voidOSStatInit(void)
{
OSTimeDly(2);
OS_ENTER_CRITICAL();
OSIdleCtr=0L;
OS_EXIT_CRITICAL();
OSTimeDly(OS_TICKS_PER_SEC);
OS_ENTER_CRITICAL();
OSIdleCtrMax=OSIdleCtr;
OSStatRdy=TRUE;
OS_EXIT_CRITICAL();
}
統(tǒng)計任務(wù)OSStat()的代碼程序清單L3.14所示。在前面一段中,已經(jīng)討論了為什么要等待統(tǒng)計任務(wù)就緒標志OSStatRdy[L3.14(1)]。這個任務(wù)每秒執(zhí)行一次,以確定所有應(yīng)用程序中的任務(wù)消耗了多少CPU時間。當用戶的應(yīng)用程序代碼加入以后,運行空閑任務(wù)的CPU時間就少了,OSIdleCtr就不會像原來什么任務(wù)都不運行時有那么多計數(shù)。要知道,OSIdleCtr的最大計數(shù)值是OSStatInit()在初始化時保存在計數(shù)器最大值OSIdleCtrMax中的。CPU利用率(表達式[3.1])是保存在變量OSCPUsage[L3.14(2)]中的:
[3.1]表達式 Needtotypesettheequation.
一旦上述計算完成,OSTaskStat()調(diào)用任務(wù)統(tǒng)計外界接入函數(shù)OSTaskStatHook()[L3.14(3)],這是一個用戶可定義的函數(shù),這個函數(shù)能使統(tǒng)計任務(wù)得到擴展。這樣,用戶可以計算并顯示所有任務(wù)總的執(zhí)行時間,每個任務(wù)執(zhí)行時間的百分比以及其它信息(參見1.09節(jié)例3)。
程序清單L3.14統(tǒng)計任務(wù)
voidOSTaskStat(void*pdata)
{
INT32Urun;
INT8Susage;
pdata=pdata;
while(OSStatRdy==FALSE){(1)
OSTimeDly(2*OS_TICKS_PER_SEC);
}
for(;;){
OS_ENTER_CRITICAL();
OSIdleCtrRun=OSIdleCtr;
run=OSIdleCtr;
OSIdleCtr=0L;
OS_EXIT_CRITICAL();
if(OSIdleCtrMax>0L){
usage=(INT8S)(100L-100L*run/OSIdleCtrMax);(2)
if(usage>100){
OSCPUUsage=100;
}elseif(usage<0){
OSCPUUsage=0;
}else{
OSCPUUsage=usage;
}
}else{
OSCPUUsage=0;
}
OSTaskStatHook();(3)
OSTimeDly(OS_TICKS_PER_SEC);
}
}
3.9 μC/OS中的中斷處理
μC/OS中,中斷服務(wù)子程序要用匯編語言來寫。然而,如果用戶使用的C語言編譯器支持在線匯編語言的話,用戶可以直接將中斷服務(wù)子程序代碼放在C語言的程序文件中。中斷服務(wù)子程序的示意碼如程序清單L3.15所示。
程序清單L3.15μC/OS-II中的中斷服務(wù)子程序.
用戶中斷服務(wù)子程序:
保存全部CPU寄存器;(1)
調(diào)用OSIntEnter或OSIntNesting直接加1; (2)
執(zhí)行用戶代碼做中斷服務(wù);(3)
調(diào)用OSIntExit(); (4)
執(zhí)行中斷返回指令; (6)
用戶代碼應(yīng)該將全部CPU寄存器推入當前任務(wù)棧[L3.15(1)]。注意,有些微處理器,例如Motorola68020(及68020以上的微處理器),做中斷服務(wù)時使用另外的堆棧。
μC/OS-Ⅱ可以用在這類微處理器中,當任務(wù)切換時,寄存器是保存在被中斷了的那個任務(wù)的棧中的。
μC/OS-Ⅱ需要知道用戶在做中斷服務(wù),故用戶應(yīng)該調(diào)用OSIntEnter(),或者將全程變量OSIntNesting[L3.15(2)]直接加1,如果用戶使用的微處理器有存儲器直接加1的單條指令的話。如果用戶使用的微處理器沒有這樣的指令,必須先將OSIntNesting讀入寄存器,再將寄存器加1,然后再寫回到變量OSIatNesting中去,就不如調(diào)用OSIatEnter()。
OSIntNesting是共享資源。OSIntEnter()把上述三條指令用開中斷、關(guān)中斷保護起來,以保證處理OSIntNesting時的排它性。直接給OSIntNesting加1比調(diào)用OSIntEnter()快得多,可能時,直接加1更好。要當心的是,在有些情況下,從OSIntEnter()返回時,會把中斷開了。遇到這種情況,在調(diào)用OSIntEnter()之前要先清中斷源,否則,中斷將連續(xù)反復(fù)打入,用戶應(yīng)用程序就會崩潰![!--empirenews.page--]
上述兩步完成以后,用戶可以開始服務(wù)于叫中斷的設(shè)備了[L3.15(3)]。這一段完全取決于應(yīng)用。μC/OS-Ⅱ允許中斷嵌套,因為μC/OS-Ⅱ跟蹤嵌套層數(shù)OSIntNesting。然而,為允許中斷嵌套,在多數(shù)情況下,用戶應(yīng)在開中斷之前先清中斷源。
調(diào)用脫離中斷函數(shù)OSIntExit()[L3.15(4)]標志著中斷服務(wù)子程序的終結(jié),OSIntExit()將中斷嵌套層數(shù)計數(shù)器減1。當嵌套計數(shù)器減到零時,所有中斷,包括嵌套的中斷就都完成了,此時μC/OS-Ⅱ要判定有沒有優(yōu)先級較高的任務(wù)被中斷服務(wù)子程序(或任一嵌套的中斷)喚醒了。如果有優(yōu)先級高的任務(wù)進入了就緒態(tài),μC/OS-Ⅱ就返回到那個高優(yōu)先級的任務(wù),OSIntExit()返回到調(diào)用點[L3.15(5)]。保存的寄存器的值是在這時恢復(fù)的,然后是執(zhí)行中斷返回指令[L3.16(6)]。注意,如果調(diào)度被禁止了(OSIntNesting>0),μC/OS-Ⅱ?qū)⒈环祷氐奖恢袛嗔说娜蝿?wù)。
以上描述的詳細解釋如圖F3.5所示。中斷來到了[F3.5(1)]但還不能被被CPU識別,也許是因為中斷被μC/OS-Ⅱ或用戶應(yīng)用程序關(guān)了,或者是因為CPU還沒執(zhí)行完當前指令。一旦CPU響應(yīng)了這個中斷[F3.5(2)],CPU的中斷向量(至少大多數(shù)微處理器是如此)跳轉(zhuǎn)到中斷服務(wù)子程序[F3.5(3)]。如上所述,中斷服務(wù)子程序保存CPU寄存器(也叫做CPUcontext)[F3.5(4)],一旦做完,用戶中斷服務(wù)子程序通知μC/OS-Ⅱ進入中斷服務(wù)子程序了,辦法是調(diào)用OSIntEnter()或者給SIntNesting直接加1[F3.5(5)]。然后用戶中斷服務(wù)代碼開始執(zhí)行[F3.5(6)]。用戶中斷服務(wù)中做的事要盡可能地少,要把大部分工作留給任務(wù)去做。中斷服務(wù)子程序通知某任務(wù)去做事的手段是調(diào)用以下函數(shù)之一:OSMboxPost(),OSQPost(),OSQPostFront(),OSSemPost()。中斷發(fā)生并由上述函數(shù)發(fā)出消息時,接收消息的任務(wù)可能是,也可能不是掛起在郵箱、隊列或信號量上的任務(wù)。用戶中斷服務(wù)完成以后,要調(diào)用OSIntExit()[F3.5(7)]。從時序圖上可以看出,對被中斷了的任務(wù)說來,如果沒有高優(yōu)先級的任務(wù)被中斷服務(wù)子程序激活而進入就緒態(tài),OSIntExit()只占用很短的運行
時間。進而,在這種情況下,CPU寄存器只是簡單地恢復(fù)[F3.5(8)]并執(zhí)行中斷返回指令
[F3.5(9)]。如果中斷服務(wù)子程序使一個高優(yōu)先級的任務(wù)進入了就緒態(tài),則OSIntExit()將占用
較長的運行時間,因為這時要做任務(wù)切換[F3.5(10)]。新任務(wù)的寄存器內(nèi)容要恢復(fù)并執(zhí)行中
斷返回指令[F3.5(12)]。
圖 3.5 中斷服務(wù)
進入中斷函數(shù) OSIntEnter()的代碼如程序清單 L3.16 所示,從中斷服務(wù)中退出函數(shù)
OSIntExit()的代碼如程序清單 L3.17 所示。如前所述,OSIntEnter()所做的事是非常少的。
程序清單 L3.16 通知μC/OS-Ⅱ,中斷服務(wù)子程序開始了.
voidOSIntEnter(void)
{
OS_ENTER_CRITICAL();
OSIntNesting++;
OS_EXIT_CRITICAL();
}
恢復(fù)所有CPU寄存器; (5)
程序清單 L3.17 通知μC/OS-Ⅱ,脫離了中斷服務(wù)
voidOSIntExit(void)
{
OS_ENTER_CRITICAL();(1)
if((--OSIntNesting|OSLockNesting)==0){(2)
OSIntExitY=OSUnMapTbl[OSRdyGrp];(3)
OSPrioHighRdy=(INT8U)((OSIntExitY<<3)+
OSUnMapTbl[OSRdyTbl[OSIntExitY]]);
if(OSPrioHighRdy!=OSPrioCur){
OSTCBHighRdy=OSTCBPrioTbl[OSPrioHighRdy];
OSCtxSwCtr++;
OSIntCtxSw();(4)
}
}
OS_EXIT_CRITICAL();
}
OSIntExit()看起來非常像OSSched()。但有三點不同。第一點,OSIntExit()使中斷
嵌套層數(shù)減1[L3.17(2)]而調(diào)度函數(shù)OSSched()的調(diào)度條件是:中斷嵌套層數(shù)計數(shù)器和鎖定
嵌套計數(shù)器(OSLockNesting)二者都必須是零。第二個不同點是,OSRdyTbl[]所需的檢索
值Y是保存在全程變量OSIntExitY中的[L3.17(3)]。這是為了避免在任務(wù)棧中安排局部變
量。這個變量在哪兒和中斷任務(wù)切換函數(shù)OSIntCtxSw()有關(guān),(見9.04.03節(jié),中斷任務(wù)
切換函數(shù))。最后一點,如果需要做任務(wù)切換,OSIntExit()將調(diào)用OSIntCtxSw()[L3.17
(4)]而不是調(diào)用OS_TASK_SW(),正像在OSSched()函數(shù)中那樣。
調(diào)用中斷切換函數(shù)OSIntCtxSw()而不調(diào)用任務(wù)切換函數(shù) OS_TASK_SW(),有兩個原因,
首先是,如程序清單中L3.5(1)和圖F3.6(1)所示,一半的工作,即CPU寄存器入棧的工作
已經(jīng)做完了。第二個原因是,在中斷服務(wù)子程序中調(diào)用OSIntExit()時,將返回地址推入
了堆棧[L3.15(4)和F3.6(2)]。OSIntExit()中的進入臨界段函數(shù)OS_ENTER_CRITICAL()或
許將CPU的狀態(tài)字也推入了堆棧L3.7(1)和F3.6(3)。這取決于中斷是怎么被關(guān)掉的(見第8
章移植μC/OS-Ⅱ)。最后,調(diào)用OSIntCtxSw()時的返回地址又被推入了堆棧[L3.17(4)和
F3.1(4)],除了棧中不相關(guān)的部分,當任務(wù)掛起時,棧結(jié)構(gòu)應(yīng)該與μC/OS-Ⅱ所規(guī)定的完全
一致。OSIntCtxSw()只需要對棧指針做簡單的調(diào)整,如圖F3.6(5)所示。換句話說,調(diào)整
棧結(jié)構(gòu)要保證所有掛起任務(wù)的棧結(jié)構(gòu)看起來是一樣的。
圖3.6中斷中的任務(wù)切換函數(shù)OSIntCtxSw()調(diào)整棧結(jié)構(gòu)
有的微處理器,像Motorola68HC11中斷發(fā)生時CPU寄存器是自動入棧的,且要想允許
中斷嵌套的話,在中斷服務(wù)子程序中要重新開中斷,這可以視作一個優(yōu)點。確實,如果用
戶中斷服務(wù)子程序執(zhí)行得非??欤脩舨恍枰ㄖ蝿?wù)自身進入了中斷服務(wù),只要不在中
斷服務(wù)期間開中斷,也不需要調(diào)用OSIntEnter()或OSIntNesting加1。程序清單L3。18
中的示意代碼表示這種情況。一個任務(wù)和這個中斷服務(wù)子程序通訊的唯一方法是通過全程
變量。
[!--empirenews.page--]程序清單 L3.18Motorola68HC11中的中斷服務(wù)子程序
M68HC11_ISR:/* 快中斷服務(wù)程序,必須禁止中斷*/
所有寄存器被CPU自動保存;
執(zhí)行用戶代碼以響應(yīng)中斷;
執(zhí)行中斷返回指令;
3.10 時鐘節(jié)拍
μC/OS需要用戶提供周期性信號源,用于實現(xiàn)時間延時和確認超時。節(jié)拍率應(yīng)在每秒
10次到100次之間,或者說10到100Hz。時鐘節(jié)拍率越高,系統(tǒng)的額外負荷就越重。時鐘
節(jié)拍的實際頻率取決于用戶應(yīng)用程序的精度。時鐘節(jié)拍源可以是專門的硬件定時器,也可
以是來自50/60Hz交流電源的信號。
用戶必須在多任務(wù)系統(tǒng)啟動以后再開啟時鐘節(jié)拍器,也就是在調(diào)用OSStart()之后。
換句話說,在調(diào)用OSStart()之后做的第一件事是初始化定時器中斷。通常,容易犯的錯
誤是將允許時鐘節(jié)拍器中斷放在系統(tǒng)初始化函數(shù)OSInit()之后,在調(diào)啟動多任務(wù)系統(tǒng)啟動
函數(shù)OSStart()之前,如程序清單L3.19所示。
程序清單L3.19啟動時鐘就節(jié)拍器的不正確做法.
voidmain(void)
{
.
.
OSInit();/* 初始化uC/OS-II*/
.
.
/* 應(yīng)用程序初始化代碼 ...*/
/*... 通過調(diào)用OSTaskCreate()創(chuàng)建至少一個任務(wù) */
.
.
允許時鐘節(jié)拍(TICKER)中斷;/* 千萬不要在這里允許時鐘節(jié)拍中斷!!! */
.
.
OSStart();/* 開始多任務(wù)調(diào)度 */
}
這里潛在地危險是,時鐘節(jié)拍中斷有可能在μC/OS-Ⅱ啟動第一個任務(wù)之前發(fā)生,此時μC/OS-Ⅱ是處在一種不確定的狀態(tài)之中,用戶應(yīng)用程序有可能會崩潰。
μC/OS-Ⅱ中的時鐘節(jié)拍服務(wù)是通過在中斷服務(wù)子程序中調(diào)用OSTimeTick()實現(xiàn)的。
時鐘節(jié)拍中斷服從所有前面章節(jié)中描述的規(guī)則。時鐘節(jié)拍中斷服務(wù)子程序的示意代碼如程序清單L3.20所示。這段代碼必須用匯編語言編寫,因為在C語言里不能直接處理CPU的寄存器。
程序清單L3.20時鐘節(jié)拍中斷服務(wù)子程序的示意代碼
voidOSTickISR(void)
{
保存處理器寄存器的值;
調(diào)用OSIntEnter()或是將OSIntNesting加1;
調(diào)用OSTimeTick();
調(diào)用OSIntExit();
恢復(fù)處理器寄存器的值;
執(zhí)行中斷返回指令;
}
時鐘節(jié)拍函數(shù)OSTimeTick()的代碼如程序清單3.21所示。OSTimtick()以調(diào)用可由用戶定義的時鐘節(jié)拍外連函數(shù)OSTimTickHook()開始,這個外連函數(shù)可以將時鐘節(jié)拍函數(shù)OSTimtick()予以擴展[L3.2(1)]。筆者決定首先調(diào)用OSTimTickHook()是打算在時鐘節(jié)拍中斷服務(wù)一開始就給用戶一個可以做點兒什么的機會,因為用戶可能會有一些時間要求苛刻的工作要做。OSTimtick()中量大的工作是給每個用戶任務(wù)控制塊OS_TCB中的時間延時項OSTCBDly減1(如果該項不為零的話)。OSTimTick()從OSTCBList開始,沿著OS_TCB鏈表做,一直做到空閑任務(wù)[L3.21(3)]。當某任務(wù)的任務(wù)控制塊中的時間延時項OSTCBDly減到了零,這個任務(wù)就進入了就緒態(tài)[L3.21(5)]。而確切被任務(wù)掛起的函數(shù)OSTaskSuspend()掛起的任務(wù)則不會進入就緒態(tài)[L3.21(4)]。OSTimTick()的執(zhí)行時間直接與應(yīng)用程序中建立了多少個任務(wù)成正比。
程序清單L3.21時鐘節(jié)拍函數(shù)OSTimtick()的一個節(jié)拍服務(wù)
voidOSTimeTick(void)
{
OS_TCB*ptcb;
OSTimeTickHook();(1)
ptcb=OSTCBList;(2)
while(ptcb->OSTCBPrio!=OS_IDLE_PRIO){(3)
OS_ENTER_CRITICAL();
if(ptcb->OSTCBDly!=0){
if(--ptcb->OSTCBDly==0){
if(!(ptcb->OSTCBStat&OS_STAT_SUSPEND)){(4)
OSRdyGrp|=ptcb->OSTCBBitY;(5)
OSRdyTbl[ptcb->OSTCBY]|=ptcb->OSTCBBitX;
}else{
ptcb->OSTCBDly=1;
}
}
}
ptcb=ptcb->OSTCBNext;
OS_EXIT_CRITICAL();
}
OS_ENTER_CRITICAL();(6)
OSTime++; (7)
OS_EXIT_CRITICAL();
}
OSTimeTick()還通過調(diào)用OSTime()[L3.21(7)]累加從開機以來的時間,用的是一個無符號32位變量。注意,在給OSTime加1之前使用了關(guān)中斷,因為多數(shù)微處理器給32位數(shù)加1的操作都得使用多條指令。
中斷服務(wù)子程序似乎就得寫這么長,如果用戶不喜歡將中斷服務(wù)程序?qū)戇@么長,可以從任務(wù)級調(diào)用OSTimeTick(),如程序清單L3.22所示。要想這么做,得建立一個高于應(yīng)用程序中所有其它任務(wù)優(yōu)先級的任務(wù)。時鐘節(jié)拍中斷服務(wù)子程序利用信號量或郵箱發(fā)信號給這個高優(yōu)先級的任務(wù)。
程序清單L3.22時鐘節(jié)拍任務(wù)TickTask()作時鐘節(jié)拍服務(wù).
voidTickTask(void*pdata)
{
pdata=pdata;
for(;;){
OSMboxPend(...);/* 等待從時鐘節(jié)拍中斷服務(wù)程序發(fā)來的信號 */
OSTimeTick();
}
}
用戶當然需要先建立一個郵箱(初始化成NULL)用于發(fā)信號給上述任何告知時鐘節(jié)拍中斷已經(jīng)發(fā)生了(程序清單L3.23)。
程序清單L3.23時鐘節(jié)拍中斷服務(wù)函數(shù)OSTickISR()做節(jié)拍服務(wù)。
voidOSTickISR(void)
{
保存處理器寄存器的值;
調(diào)用OSIntEnter()或是將OSIntNesting加1;
發(fā)送一個‘空’消息(例如, (void*)1)到時鐘節(jié)拍的郵箱;
調(diào)用OSIntExit();
恢復(fù)處理器寄存器的值;
執(zhí)行中斷返回指令;
}
3.11 μC/OS-Ⅱ初始化
在調(diào)用μC/OS-Ⅱ的任何其它服務(wù)之前,μC/OS-Ⅱ要求用戶首先調(diào)用系統(tǒng)初始化函數(shù)OSIint()。OSIint()初始化μC/OS-Ⅱ所有的變量和數(shù)據(jù)結(jié)構(gòu)(見OS_CORE.C)。
OSInit()建立空閑任務(wù)idle task,這個任務(wù)總是處于就緒態(tài)的??臻e任務(wù)OSTaskIdle()的優(yōu)先級總是設(shè)成最低,即OS_LOWEST_PRIO。如果統(tǒng)計任務(wù)允許OS_TASK_STAT_EN和任務(wù)建立擴展允許都設(shè)為1,則OSInit()還得建立統(tǒng)計任務(wù)OSTaskStat()并且讓其進入就緒態(tài)。OSTaskStat的優(yōu)先級總是設(shè)為OS_LOWEST_PRIO-1。
圖F3.7表示調(diào)用OSInit()之后,一些μC/OS-Ⅱ變量和數(shù)據(jù)結(jié)構(gòu)之間的關(guān)系。其解釋是基于以下假設(shè)的:[!--empirenews.page--]
z 在文件OS_CFG.H中,OS_TASK_STAT_EN是設(shè)為1的。
z 在文件OS_CFG.H中,OS_LOWEST_PRIO是設(shè)為63的。
z 在文件OS_CFG.H中,最多任務(wù)數(shù)OS_MAX_TASKS是設(shè)成大于2的。
以上兩個任務(wù)的任務(wù)控制塊(OS_TCBs)是用雙向鏈表鏈接在一起的。OSTCBList指向這個鏈表的起始處。當建立一個任務(wù)時,這個任務(wù)總是被放在這個鏈表的起始處。換句話說,OSTCBList總是指向最后建立的那個任務(wù)。鏈的終點指向空字符NULL(也就是零)。
因為這兩個任務(wù)都處在就緒態(tài),在就緒任務(wù)表OSRdyTbl[]中的相應(yīng)位是設(shè)為1的。還有,因為這兩個任務(wù)的相應(yīng)位是在OSRdyTbl[]的同一行上,即屬同一組,故OSRdyGrp中只有1位是設(shè)為1的。
μC/OS-Ⅱ還初始化了4個空數(shù)據(jù)結(jié)構(gòu)緩沖區(qū),如圖F3.8所示。每個緩沖區(qū)都是單向鏈
表,允許μC/OS-Ⅱ從緩沖區(qū)中迅速得到或釋放一個緩沖區(qū)中的元素。注意,空任務(wù)控制塊
在空緩沖區(qū)中的數(shù)目取決于最多任務(wù)數(shù)OS_MAX_TASKS,這個最多任務(wù)數(shù)是在OS_CFG.H文件
中定義的。μC/OS-Ⅱ自動安排總的系統(tǒng)任務(wù)數(shù)OS_N_SYS_TASKS(見文件μC/OS-Ⅱ.H)。控
制塊OS_TCB的數(shù)目也就自動確定了。當然,包括足夠的任務(wù)控制塊分配給統(tǒng)計任務(wù)和空閑
任務(wù)。指向空事件表OSEventFreeList和空隊列表OSFreeList的指針將在第6章,任務(wù)間
通訊與同步中討論。指向空存儲區(qū)的指針表OSMemFreeList將在第7章存儲管理中討論。
3.12 μC/OS-Ⅱ的啟動
多任務(wù)的啟動是用戶通過調(diào)用 OSStart()實現(xiàn)的。然而,啟動μC/OS-Ⅱ之前,用戶至少
要建立一個應(yīng)用任務(wù),如程序清單L3.24所示。
程序清單L3.24初始化和啟動μC/OS-Ⅱ
voidmain(void)
{
OSInit();/* 初始化uC/OS-II*/
.
.
通過調(diào)用OSTaskCreate()或OSTaskCreateExt()創(chuàng)建至少一個任務(wù);
.
.
OSStart();/* 開始多任務(wù)調(diào)度!OSStart()永遠不會返回 */
}
圖3.7調(diào)用OSInit()之后的數(shù)據(jù)結(jié)構(gòu)
圖3.8空緩沖區(qū)
OSStart()的代碼如程序清單L3.25所示。當調(diào)用OSStart()時,OSStart()從任務(wù)就緒表中找出那個用戶建立的優(yōu)先級最高任務(wù)的任務(wù)控制塊[L3.25(1)]。然后,OSStart()調(diào)用高優(yōu)先級就緒任務(wù)啟動函數(shù) OSStartHighRdy()[L3,25(2)],(見匯編語言文件 OS_CPU_A.ASM),這個文件與選擇的微處理器有關(guān)。實質(zhì)上,函數(shù)OSStartHighRdy()是將任務(wù)棧中保存的值彈回到 CPU 寄存器中,然后執(zhí)行一條中斷返回指令,中斷返回指令強制執(zhí)行該任務(wù)代碼。見9.04.01節(jié),高優(yōu)先級就緒任務(wù)啟動函數(shù) OSStartHighRdy()。那一節(jié)詳細介紹對于 80x86微處理器是怎么做的。注意,OSStartHighRdy()將永遠不返回到 OSStart()。
程序清單 L3.25 啟動多任務(wù).
voidOSStart(void)
{
INT8Uy;
INT8Ux;
if(OSRunning==FALSE){
y=OSUnMapTbl[OSRdyGrp];
x=OSUnMapTbl[OSRdyTbl[y]];
OSPrioHighRdy=(INT8U)((y<<3)+x);
OSPrioCur=OSPrioHighRdy;
OSTCBHighRdy=OSTCBPrioTbl[OSPrioHighRdy];(1)
OSTCBCur=OSTCBHighRdy;
OSStartHighRdy();(2)
}
}
多任務(wù)啟動以后變量與數(shù)據(jù)結(jié)構(gòu)中的內(nèi)容如圖 F3.9 所示。這里筆者假設(shè)用戶建立的任務(wù)優(yōu)先級為 6,注意,OSTaskCtr指出已經(jīng)建立了 3 個任務(wù)。OSRunning已設(shè)為“真” ,指出多任務(wù)已經(jīng)開始, OSPrioCur和 OSPrioHighRdy存放的是用戶應(yīng)用任務(wù)的優(yōu)先級, OSTCBCur和 OSTCBHighRdy二者都指向用戶任務(wù)的任務(wù)控制塊。
3.13 獲取當前μC/OS-Ⅱ的版本號
應(yīng)用程序調(diào)用OSVersion()[程序清單L3.26]可以得到當前μC/OS-Ⅱ的版本號。
OSVersion()函數(shù)返回版本號值乘以100。換言之,200表示版本號2.00。
程序清單L3.26得到μC/OS-Ⅱ當前版本號
INT16UOSVersion(void)
{
return(OS_VERSION);
}
為找到μC/OS-Ⅱ的最新版本以及如何做版本升級,用戶可以與出版商聯(lián)系,或者查看
μC/OS-Ⅱ得正式網(wǎng)站W(wǎng)WW.uCOS-II.COM
圖3.9調(diào)用OSStart()以后的變量與數(shù)據(jù)結(jié)構(gòu)
3.14 OSEvent()函數(shù)
讀者或許注意到有4個OS_CORE.C中的函數(shù)沒有在本章中提到。這4個函數(shù)是
OSEventWaitListInit(),OSEventTaskRdy(),OSEventTaskWait(),OSEventTO()。這幾個
函數(shù)是放在文件OS_CORE.C中的,而對如何使用這個函數(shù)的解釋見第6章,任務(wù)間的通訊與
同步。