當前位置:首頁 > 嵌入式 > 嵌入式軟件
[導讀] μC/OS-Ⅱ是怎樣處理臨界段代碼的;什么是任務,怎樣把用戶的任務交給μC/OS-Ⅱ;任務是怎樣調度的;應用程序CPU的利用率是多少,μC/OS-Ⅱ是怎樣知道的;怎樣寫中斷服務

 μC/OS-Ⅱ是怎樣處理臨界段代碼的;

什么是任務,怎樣把用戶的任務交給μC/OS-Ⅱ;

任務是怎樣調度的;

應用程序CPU的利用率是多少,μC/OS-Ⅱ是怎樣知道的;

怎樣寫中斷服務子程序;

什么是時鐘節(jié)拍,μC/OS-Ⅱ是怎樣處理時鐘節(jié)拍的;

μC/OS-Ⅱ是怎樣初始化的,以及

 怎樣啟動多任務;

本章還描述以下函數(shù),這些服務于應用程序:

OS_ENTER_CRITICAL() 和 OS_EXIT_CRITICAL(),

 OSInit(),

OSSTart(),

OSIntEnter() 和 OSIntExit(),

OSSchedLock() 和 OSSchedUnlock(), 以及

OSVersiON().

3.0 臨界段(CriticalSections)

和其它內核一樣,μC/OS-Ⅱ為了處理臨界段代碼需要關中斷,處理完畢后再開中斷。這使得μC/OS-Ⅱ能夠避免同時有其它任務或中斷服務進入臨界段代碼。關中斷的時間是實時內核開發(fā)商應提供的最重要的指標之一,因為這個指標影響用戶系統(tǒng)對實時事件的響應性。μC/OS-Ⅱ努力使關中斷時間降至最短,但就使用μC/OS-Ⅱ而言,關中斷的時間很大程度上取決于微處理器的架構以及編譯器所生成的代碼質量。

微處理器一般都有關中斷/開中斷指令,用戶使用的C語言編譯器必須有某種機制能夠在C中直接實現(xiàn)關中斷/開中斷地操作。某些C編譯器允許在用戶的C源代碼中插入?yún)R編語言的語句。這使得插入微處理器指令來關中斷/開中斷很容易實現(xiàn)。而有的編譯器把從C語言中關中斷/開中斷放在語言的擴展部分。μC/OS-Ⅱ定義兩個宏(macros)來關中斷和開中斷,以便避開不同C編譯器廠商選擇不同的方法來處理關中斷和開中斷。μC/OS-Ⅱ中的這兩個宏調用分別是:OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()。因為這兩個宏的定義取決于所用的微處理器,故在文件OS_CPU.H中可以找到相應宏定義。每種微處理器都有自己的OS_CPU.H文件。

3.1 任務

一個任務通常是一個無限的循環(huán)[L3.1(2)],如程序清單3.1所示。一個任務看起來像其它C的函數(shù)一樣,有函數(shù)返回類型,有形式參數(shù)變量,但是任務是絕不會返回的。故返回參數(shù)必須定義成void[L3.1(1)]。

程序清單L3.1任務是一個無限循環(huán)

voidYourTask(void*pdata)(1)

{

for(;;){(2)

/* 用戶代碼 */

調用uC/OS-II的某種系統(tǒng)服務:

OSMboxPend();

OSQPend();

OSSemPend();

OSTaskDel(OS_PRIO_SELF);

OSTaskSuspend(OS_PRIO_SELF);

OSTimeDly();

OSTimeDlyHMSM();

/* 用戶代碼 */

}

}

不同的是,當任務完成以后,任務可以自我刪除,如清單L3.2所示。注意任務代碼并非真的刪除了,μC/OS-Ⅱ只是簡單地不再理會這個任務了,這個任務的代碼也不會再運行,如果任務調用了OSTaskDel(),這個任務絕不會返回什么。

程序清單 L3.2. 任務完成后自我刪除

voidYourTask(void*pdata)

{

/* 用戶代碼 */

OSTaskDel(OS_PRIO_SELF);

}

形式參數(shù)變量[L3.1(1)]是由用戶代碼在第一次執(zhí)行的時候帶入的。請注意,該變量的類型是一個指向void的指針。這是為了允許用戶應用程序傳遞任何類型的數(shù)據(jù)給任務。這個指針好比一輛萬能的車子,如果需要的話,可以運載一個變量的地址,或一個結構,甚至是一個函數(shù)的地址。也可以建立許多相同的任務,所有任務都使用同一個函數(shù)(或者說是同一個任務代碼程序),見第一章的例1。例如,用戶可以將四個串行口安排成每個串行口都是一個單獨的任務,而每個任務的代碼實際上是相同的。并不需要將代碼復制四次,用戶可以建立一個任務,向這個任務傳入一個指向某數(shù)據(jù)結構的指針變量,這個數(shù)據(jù)結構定義串行口的參數(shù)(波特率、I/O口地址、中斷向量號等)。

μC/OS-Ⅱ可以管理多達64個任務,但目前版本的μC/OS-Ⅱ有兩個任務已經(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個任務以被將來使用。OS_LOWEST_PRI0是作為定義的常數(shù)在OS_CFG.H文件中用定義常數(shù)語句#defineconstant定義的。因此用戶可以有多達56個應用任務。必須給每個任務賦以不同的優(yōu)先級,優(yōu)先級可以從0到OS_LOWEST_PR10-2。優(yōu)先級號越低,任務的優(yōu)先級越高。μC/OS-Ⅱ總是運行進入就緒態(tài)的優(yōu)先級最高的任務。目前版本的μC/OS-Ⅱ中,任務的優(yōu)先級號就是任務編號(ID)。優(yōu)先級號(或任務的ID號)也被一些內核服務函數(shù)調用,如改變優(yōu)先級函數(shù)OSTaskChangePrio(),以及任務刪除函數(shù)OSTaskDel()。

為了使μC/OS-Ⅱ能管理用戶任務,用戶必須在建立一個任務的時候,將任務的起始地址與其它參數(shù)一起傳給下面兩個函數(shù)中的一個:OSTastCreat或OSTaskCreatExt()。

OSTaskCreateExt()是OSTaskCreate()的擴展,擴展了一些附加的功能。,這兩個函數(shù)的解釋見第四章,任務管理。

3.2 任務狀態(tài)

圖3.1是μC/OS-Ⅱ控制下的任務狀態(tài)轉換圖。在任一給定的時刻,任務的狀態(tài)一定是在這五種狀態(tài)之一。

睡眠態(tài)(DORMANT)指任務駐留在程序空間之中,還沒有交給μC/OS-Ⅱ管理,(見程序清單L3.1或L3.2)。把任務交給μC/OS-Ⅱ是通過調用下述兩個函數(shù)之一:

OSTaskCreate()或OSTaskCreateExt()。當任務一旦建立,這個任務就進入就緒態(tài)準備運行。任務的建立可以是在多任務運行開始之前,也可以是動態(tài)地被一個運行著的任務建立。如果一個任務是被另一個任務建立的,而這個任務的優(yōu)先級高于建立它的那個任務,則這個剛剛建立的任務將立即得到CPU的控制權。一個任務可以通過調用OSTaskDel()返回到睡眠態(tài),或通過調用該函數(shù)讓另一個任務進入睡眠態(tài)。

調用OSStart()可以啟動多任務。OSStart()函數(shù)運行進入就緒態(tài)的優(yōu)先級最高的任務。就緒的任務只有當所有優(yōu)先級高于這個任務的任務轉為等待狀態(tài),或者是被刪除了,才能進入運行態(tài)。

[!--empirenews.page--]

圖3.1任務的狀態(tài)

正在運行的任務可以通過調用兩個函數(shù)之一將自身延遲一段時間,這兩個函數(shù)是OSTimeDly()或OSTimeDlyHMSM()。這個任務于是進入等待狀態(tài),等待這段時間過去,下一個優(yōu)先級最高的、并進入了就緒態(tài)的任務立刻被賦予了CPU的控制權。等待的時間過去以后,系統(tǒng)服務函數(shù)OSTimeTick()使延遲了的任務進入就緒態(tài)(見3.10節(jié),時鐘節(jié)拍)。

正在運行的任務期待某一事件的發(fā)生時也要等待,手段是調用以下3個函數(shù)之一:

OSSemPend(),OSMboxPend(),或OSQPend()。調用后任務進入了等待狀態(tài)(WAITING)。當任務因等待事件被掛起(Pend),下一個優(yōu)先級最高的任務立即得到了CPU的控制權。當事件發(fā)生了,被掛起的任務進入就緒態(tài)。事件發(fā)生的報告可能來自另一個任務,也可能來自中斷服務子程序。

正在運行的任務是可以被中斷的,除非該任務將中斷關了,或者μC/OS-Ⅱ將中斷關了。被中斷了的任務就進入了中斷服務態(tài)(ISR)。響應中斷時,正在執(zhí)行的任務被掛起,中斷服務子程序控制了CPU的使用權。中斷服務子程序可能會報告一個或多個事件的發(fā)生,而使一個或多個任務進入就緒態(tài)。在這種情況下,從中斷服務子程序返回之前,μC/OS-Ⅱ要判定,被中斷的任務是否還是就緒態(tài)任務中優(yōu)先級最高的。如果中斷服務子程序使一個優(yōu)先級更高的任務進入了就緒態(tài),則新進入就緒態(tài)的這個優(yōu)先級更高的任務將得以運行,否則原來被中斷了的任務才能繼續(xù)運行。當所有的任務都在等待事件發(fā)生或等待延遲時間結束,μC/OS-Ⅱ執(zhí)行空閑任務(idletask),執(zhí)行OSTaskIdle()函數(shù)。

3.3 任務控制塊(TaskControlBlocks,OS _TCBs)

一旦任務建立了,任務控制塊OS _TCBs將被賦值(程序清單3.3)。任務控制塊是一個數(shù)據(jù)結構,當任務的CPU使用權被剝奪時,μC/OS-Ⅱ用它來保存該任務的狀態(tài)。當任務重新得到CPU使用權時,任務控制塊能確保任務從當時被中斷的那一點絲毫不差地繼續(xù)執(zhí)行。OS _TCBs全部駐留在RAM中。讀者將會注意到筆者在組織這個數(shù)據(jù)結構時,考慮到了各成員的邏輯分組。任務建立的時候,OS _TCBs就被初始化了(見第四章任務管理)。

程序清單 L3.3μC/OS-II任務控制塊.

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 是指向當前任務棧頂?shù)闹羔槨?mu;C/OS-Ⅱ允許每個任務有自己的棧,尤為重

要的是,每個任務的棧的容量可以是任意的。有些商業(yè)內核要求所有任務棧的容量都一

樣,除非用戶寫一個復雜的接口函數(shù)來改變之。這種限制浪費了RAM,當各任務需要的棧

空間不同時,也得按任務中預期棧容量需求最多的來分配棧空間。OSTCBStkPtr是OS_TCB

數(shù)據(jù)結構中唯一的一個能用匯編語言來處置的變量(在任務切換段的代碼Context-

switchingcode之中,)把OSTCBStkPtr放在數(shù)據(jù)結構的最前面,使得從匯編語言中處理

這個變量時較為容易。

.OSTCBExtPtr 指向用戶定義的任務控制塊擴展。用戶可以擴展任務控制塊而不必修改μ

C/OS-Ⅱ的源代碼。.OSTCBExtPtr只在函數(shù)OstaskCreateExt()中使用,故使用時要將

OS_TASK_CREAT_EN設為1,以允許建立任務函數(shù)的擴展。例如用戶可以建立一個數(shù)據(jù)結

構,這個數(shù)據(jù)結構包含每個任務的名字,或跟蹤某個任務的執(zhí)行時間,或者跟蹤切換到某

個任務的次數(shù)(見例3)。注意,筆者將這個擴展指針變量放在緊跟著堆棧指針的位置,

為的是當用戶需要在匯編語言中處理這個變量時,從數(shù)據(jù)結構的頭上算偏移量比較方便。

.OSTCBStkBottom 是指向任務棧底的指針。如果微處理器的棧指針是遞減的,即棧存儲器

從高地址向低地址方向分配,則OSTCBStkBottom指向任務使用的??臻g的最低地址。類似

地,如果微處理器的棧是從低地址向高地址遞增型的,則OSTCBStkBottom指向任務可以使

用的棧空間的最高地址。函數(shù)OSTaskStkChk()要用到變量OSTCBStkBottom,在運行中檢驗

??臻g的使用情況。用戶可以用它來確定任務實際需要的棧空間。這個功能只有當用戶在

任務建立時允許使用OSTaskCreateExt()函數(shù)時才能實現(xiàn)。這就要求用戶將

OS_TASK_CREATE_EXT_EN設為1,以便允許該功能。

.OSTCBStkSize 存有棧中可容納的指針元數(shù)目而不是用字節(jié)(Byte)表示的棧容量總數(shù)。

也就是說,如果棧中可以保存1,000個入口地址,每個地址寬度是32位的,則實際棧容量

是4,000字節(jié)。同樣是1,000個入口地址,如果每個地址寬度是16位的,則總棧容量只有

2,000字節(jié)。在函數(shù)OSStakChk()中要調用OSTCBStkSize。同理,若使用該函數(shù)的話,要將

OS_TASK_CREAT_EXT_EN設為1。

.OSTCBOpt 把“選擇項”傳給OSTaskCreateExt(),只有在用戶將OS_TASK_CREATE_EXT_EN

設為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(),在任務建立的時候任務棧檢驗功能得

到了允許。OS_TASK_OPT_STK_CLR表示任務建立的時候任務棧要清零。只有在用戶需要有

棧檢驗功能時,才需要將棧清零。如果不定義OS_TASK_OPT_STK_CLR,而后又建立、刪除[!--empirenews.page--]

了任務,棧檢驗功能報告的棧使用情況將是錯誤的。如果任務一旦建立就決不會被刪除,

而用戶初始化時,已將RAM清過零,則OS_TASK_OPT_STK_CLR不需要再定義,這可以節(jié)約程

序執(zhí)行時間。傳遞了OS_TASK_OPT_STK_CLR將增加TaskCreateExt()函數(shù)的執(zhí)行時間,因為

要將??臻g清零。棧容量越大,清零花的時間越長。最后一個選擇項

OS_TASK_OPT_SAVE_FP通知TaskCreateExt(),任務要做浮點運算。如果微處理器有硬件的

浮點協(xié)處理器,則所建立的任務在做任務調度切換時,浮點寄存器的內容要保存。

.OSTCBId用于存儲任務的識別碼。這個變量現(xiàn)在沒有使用,留給將來擴展用。

.OSTCBNext 和.OSTCBPrev 用于任務控制塊OS_TCBs的雙重鏈接,該鏈表在時鐘節(jié)拍函數(shù)

OSTimeTick()中使用,用于刷新各個任務的任務延遲變量.OSTCBDly,每個任務的任務控制

塊OS_TCB在任務建立的時候被鏈接到鏈表中,在任務刪除的時候從鏈表中被刪除。雙重連

接的鏈表使得任一成員都能被快速插入或刪除。

.OSTCBEventPtr是指向事件控制塊的指針,后面的章節(jié)中會有所描述(見第6章任務間通

訊與同步)。

.OSTCBMsg是指向傳給任務的消息的指針。用法將在后面的章節(jié)中提到(見第6章任務間通

訊與同步)。

.OSTCBDly 當需要把任務延時若干時鐘節(jié)拍時要用到這個變量,或者需要把任務掛起一段

時間以等待某事件的發(fā)生,這種等待是有超時限制的。在這種情況下,這個變量保存的是

任務允許等待事件發(fā)生的最多時鐘節(jié)拍數(shù)。如果這個變量為0,表示任務不延時,或者表

示等待事件發(fā)生的時間沒有限制。

.OSTCBStat是任務的狀態(tài)字。當.OSTCBStat為0,任務進入就緒態(tài)??梢越o.OSTCBStat賦

其它的值,在文件uCOS_II.H中有關于這個值的描述。

.OSTCBPrio是任務優(yōu)先級。高優(yōu)先級任務的.OSTCBPrio值小。也就是說,這個值越小,任

務的優(yōu)先級越高。

.OSTCBX,.OSTCBY,.OSTCBBitX和.OSTCBBitY 用于加速任務進入就緒態(tài)的過程或進入等

待事件發(fā)生狀態(tài)的過程(避免在運行中去計算這些值)。這些值是在任務建立時算好的,

或者是在改變任務優(yōu)先級時算出的。這些值的算法見程序清單L3.4。

程序清單 L3.4 任務控制塊OS_TCB中幾個成員的算法

OSTCBY=priority>>3;

OSTCBBitY=OSMapTbl[priority>>3];

OSTCBX=priority&0x07;

OSTCBBitX=OSMapTbl[priority&0x07];

.OSTCBDelReq是一個布爾量,用于表示該任務是否需要刪除,用法將在后面的章節(jié)中描述

(見第4章任務管理)

應用程序中可以有的最多任務數(shù)(OS_MAX_TASKS)是在文件OS_CFG.H中定義的。這個

最多任務數(shù)也是μC/OS-Ⅱ分配給用戶程序的最多任務控制塊OS_TCBs的數(shù)目。將

OS_MAX_TASKS的數(shù)目設置為用戶應用程序實際需要的任務數(shù)可以減小RAM的需求量。所有

的任務控制塊OS_TCBs都是放在任務控制塊列表數(shù)組OSTCBTbl[]中的。請注意,μC/OS-Ⅱ

分配給系統(tǒng)任務OS_N_SYS_TASKS若干個任務控制塊,見文件μC/OS-Ⅱ.H,供其內部使

用。目前,一個用于空閑任務,另一個用于任務統(tǒng)計(如果OS_TASK_STAT_EN是設為

1的)。在μC/OS-Ⅱ初始化的時候,如圖3.2所示,所有任務控制塊OS_TCBs被鏈接成單

向空任務鏈表。當任務一旦建立,空任務控制塊指針OSTCBFreeList指向的任務控制塊便

賦給了該任務,然后OSTCBFreeList的值調整為指向下鏈表中下一個空的任務控制塊。一

旦任務被刪除,任務控制塊就還給空任務鏈表。

圖3.2空任務列表

3.4 就緒表(ReadyList)

每個任務被賦予不同的優(yōu)先級等級,從0級到最低優(yōu)先級OS_LOWEST_PR1O,包括0和S_LOWEST_PR1O在內(見文件OS_CFG.H)。當μC/OS-Ⅱ初始化的時候,最低優(yōu)先級OS_LOWEST_PR1O總是被賦給空閑任務idletask。注意,最多任務數(shù)目OS_MAX_TASKS和最低優(yōu)先級數(shù)是沒有關系的。用戶應用程序可以只有10個任務,而仍然可以有32個優(yōu)先級的級別(如果用戶將最低優(yōu)先級數(shù)設為31的話)。

每個任務的就緒態(tài)標志都放入就緒表中的,就緒表中有兩個變量OSRedyGrp和OSRdyTbl[]。在OSRdyGrp中,任務按優(yōu)先級分組,8個任務為一組。OSRdyGrp中的每一位表示8組任務中每一組中是否有進入就緒態(tài)的任務。任務進入就緒態(tài)時,就緒表OSRdyTbl[]中的相應元素的相應位也置位。就緒表OSRdyTbl[]數(shù)組的大小取決于OS_LOWEST_PR1O(見文件OS_CFG.H)。當用戶的應用程序中任務數(shù)目比較少時,減少OS_LOWEST_PR1O的值可以降低μC/OS-Ⅱ對RAM(數(shù)據(jù)空間)的需求量。

為確定下次該哪個優(yōu)先級的任務運行了,內核調度器總是將OS_LOWEST_PR1O在就緒表中相應字節(jié)的相應位置1。OSRdyGrp和OSRdyTbl[]之間的關系見圖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中的代碼用于將任務放入就緒表。Prio是任務的優(yōu)先級。

程序清單L3.5使任務進入就緒態(tài)

OSRdyGrp|=OSMapTbl[prio>>3];

OSRdyTbl[prio>>3]|=OSMapTbl[prio&0x07];

表T3.1OSMapTbl[]的值

[!--empirenews.page--]

讀者可以看出,任務優(yōu)先級的低三位用于確定任務在總就緒表OSRdyTbl[]中的所在位。接下去的三位用于確定是在OSRdyTbl[]數(shù)組的第幾個元素。OSMapTbl[]是在ROM中的(見文件OS_CORE.C)屏蔽字,用于限制OSRdyTbl[]數(shù)組的元素下標在0到7之間,見表3.1

圖3.3μC/OS-Ⅱ就緒表

如果一個任務被刪除了,則用程序清單3.6中的代碼做求反處理。

程序清單L3.6從就緒表中刪除一個任務

if((OSRdyTbl[prio>>3]&=~OSMapTbl[prio&0x07])==0)

OSRdyGrp&=~OSMapTbl[prio>>3];

以上代碼將就緒任務表數(shù)組OSRdyTbl[]中相應元素的相應位清零,而對于OSRdyGrp,

只有當被刪除任務所在任務組中全組任務一個都沒有進入就緒態(tài)時,才將相應位清零。也

就是說OSRdyTbl[prio>>3]所有的位都是零時,OSRdyGrp的相應位才清零。為了找到那個

進入就緒態(tài)的優(yōu)先級最高的任務,并不需要從OSRdyTbl[0]開始掃描整個就緒任務表,只

需要查另外一張表,即優(yōu)先級判定表OSUnMapTbl([256])(見文件 OS_CORE.C)。OSRdyTbl[]

中每個字節(jié)的8位代表這一組的8個任務哪些進入就緒態(tài)了,低位的優(yōu)先級高于高位。利用

這個字節(jié)為下標來查OSUnMapTbl這張表,返回的字節(jié)就是該組任務中就緒態(tài)任務中優(yōu)先級

最高的那個任務所在的位置。這個返回值在0到7之間。確定進入就緒態(tài)的優(yōu)先級最高的任

務是用以下代碼完成的,如程序清單L3.7所示。

程序清單 L3.7 找出進入就緒態(tài)的優(yōu)先級最高的任務

y=OSUnMapTbl[OSRdyGrp];

x=OSUnMapTbl[OSRdyTbl[y]];

prio=(y<<3)+x;

例如,如果OSRdyGrp的值為二進制01101000,查OSUnMapTbl[OSRdyGrp]得到的值是

3,它相應于OSRdyGrp中的第3位bit3,這里假設最右邊的一位是第0位bit0。類似地,

如果OSRdyTbl[3]的值是二進制11100100,則OSUnMapTbl[OSRdyTbc[3]]的值是2,即第2

位。于是任務的優(yōu)先級Prio就等于26(3*8+2)。利用這個優(yōu)先級的值。查任務控制塊優(yōu)

先級表OSTCBPrioTbl[],得到指向相應任務的任務控制塊OS_TCB的工作就完成了。

3.5 任務調度(TaskScheduling)

μC/OS-Ⅱ總是運行進入就緒態(tài)任務中優(yōu)先級最高的那一個。確定哪個任務優(yōu)先級最

高,下面該哪個任務運行了的工作是由調度器(Scheduler)完成的。任務級的調度是由函

數(shù)OSSched()完成的。中斷級的調度是由另一個函數(shù)OSIntExt()完成的,這個函數(shù)將在以

后描述。OSSched()的代碼如程序清單L3.8所示。

程序清單L3.8任務調度器(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-Ⅱ任務調度所花的時間是常數(shù),與應用程序中建立的任務數(shù)無關。如程序清單

中[L3.8(1)]條件語句的條件不滿足,任務調度函數(shù)OSSched()將退出,不做任務調度。這

個條件是:如果在中斷服務子程序中調用OSSched(),此時中斷嵌套層數(shù)

OSIntNesting>0,或者由于用戶至少調用了一次給任務調度上鎖函數(shù)OSSchedLock(),使

OSLockNesting>0。如果不是在中斷服務子程序調用OSSched(),并且任務調度是允許的,

即沒有上鎖,則任務調度函數(shù)將找出那個進入就緒態(tài)且優(yōu)先級最高的任務[L3.8(2)],進入

就緒態(tài)的任務在就緒任務表中有相應的位置位。一旦找到那個優(yōu)先級最高的任務,

OSSched()檢驗這個優(yōu)先級最高的任務是不是當前正在運行的任務,以此來避免不必要的任

務調度[L3.8(3)]。注意,在μC/OS中曾經(jīng)是先得到OSTCBHighRdy然后和OSTCBCur做比

較。因為這個比較是兩個指針型變量的比較,在8位和一些16位微處理器中這種比較相對

較慢。而在μC/OS-Ⅱ中是兩個整數(shù)的比較。并且,除非用戶實際需要做任務切換,在查任

務控制塊優(yōu)先級表OSTCBPrioTbl[]時,不需要用指針變量來查OSTCBHighRdy。綜合這兩項

改進,即用整數(shù)比較代替指針的比較和當需要任務切換時再查表,使得μC/OS-Ⅱ比μC/OS

在8位和一些16位微處理器上要更快一些。

為實現(xiàn)任務切換,OSTCBHighRdy必須指向優(yōu)先級最高的那個任務控制塊OS_TCB,這是

通過將以OSPrioHighRdy為下標的OSTCBPrioTbl[]數(shù)組中的那個元素賦給OSTCBHighRdy來

實現(xiàn)的[L3.8(4)]。接著,統(tǒng)計計數(shù)器OSCtxSwCtr加1,以跟蹤任務切換次數(shù)[L3.8(5)]。

最后宏調用OS_TASK_SW()來完成實際上的任務切換[L3.8(6)]。

任務切換很簡單,由以下兩步完成,將被掛起任務的微處理器寄存器推入堆棧,然后

將較高優(yōu)先級的任務的寄存器值從棧中恢復到寄存器中。在μC/OS-Ⅱ中,就緒任務的棧結

構總是看起來跟剛剛發(fā)生過中斷一樣,所有微處理器的寄存器都保存在棧中。換句話說,

μC/OS-Ⅱ運行就緒態(tài)的任務所要做的一切,只是恢復所有的CPU寄存器并運行中斷返回指

令。為了做任務切換,運行OS_TASK_SW(),人為模仿了一次中斷。多數(shù)微處理器有軟中斷

指令或者陷阱指令TRAP來實現(xiàn)上述操作。中斷服務子程序或陷阱處理(Traphardler),

也稱作事故處理(exceptionhandler),必須提供中斷向量給匯編語言函數(shù)OSCtxSw()。

OSCtxSw()除了需要OS_TCBHighRdy指向即將被掛起的任務,還需要讓當前任務控制塊

OSTCBCur指向即將被掛起的任務,參見第8章,移植μC/OS-Ⅱ,有關于OSCtxSw()的更詳[!--empirenews.page--]

盡的解釋。

OSSched()的所有代碼都屬臨界段代碼。在尋找進入就緒態(tài)的優(yōu)先級最高的任務過程

中,為防止中斷服務子程序把一個或幾個任務的就緒位置位,中斷是被關掉的。為縮短切

換時間,OSSched()全部代碼都可以用匯編語言寫。為增加可讀性,可移植性和將匯編語言

代碼最少化,OSSched()是用C寫的。

3.6 給調度器上鎖和開鎖(LockingandUnLockingtheScheduler)

給調度器上鎖函數(shù)OSSchedlock()(程序清單L3.9)用于禁止任務調度,直到任務完

成后調用給調度器開鎖函數(shù)OSSchedUnlock()為止,(程序清單L3.10)。調用

OSSchedlock()的任務保持對CPU的控制權,盡管有個優(yōu)先級更高的任務進入了就緒態(tài)。然

而,此時中斷是可以被識別的,中斷服務也能得到(假設中斷是開著的)。OSSchedlock()

和OSSchedUnlock()必須成對使用。變量OSLockNesting跟蹤OSSchedLock()函數(shù)被調用的

次數(shù),以允許嵌套的函數(shù)包含臨界段代碼,這段代碼其它任務不得干預。μC/OS-Ⅱ允許嵌

套深度達255層。當OSLockNesting等于零時,調度重新得到允許。函數(shù)OSSchedLock()和

OSSchedUnlock()的使用要非常謹慎,因為它們影響μC/OS-Ⅱ對任務的正常管理。

當OSLockNesting減到零的時候,OSSchedUnlock()調用OSSched[L3.10(2)]。

OSSchedUnlock()是被某任務調用的,在調度器上鎖的期間,可能有什么事件發(fā)生了并使一

個更高優(yōu)先級的任務進入就緒態(tài)。

調用OSSchedLock()以后,用戶的應用程序不得使用任何能將現(xiàn)行任務掛起的系統(tǒng)調用。也就是說,用戶程序不得調用OSMboxPend()、OSQPend()、OSSemPend()、OSTaskSuspend

(OS_PR1O_SELF)、OSTimeDly()或OSTimeDlyHMSM(),直到OSLockNesting回零為止。因為調度器上了鎖,用戶就鎖住了系統(tǒng),任何其它任務都不能運行。

當?shù)蛢?yōu)先級的任務要發(fā)消息給多任務的郵箱、消息隊列、信號量時(見第6章任務間通訊和同步),用戶不希望高優(yōu)先級的任務在郵箱、隊列和信號量沒有得到消息之前就取得了CPU的控制權,此時,用戶可以使用禁止調度器函數(shù)。

程序清單 L3.9 給調度器上鎖

voidOSSchedLock(void)

{

if(OSRunning==TRUE){

OS_ENTER_CRITICAL();

OSLockNesting++;

OS_EXIT_CRITICAL();

}

}

程序清單L3.10給調度器開鎖.

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 空閑任務(IdleTask)

μC/OS-Ⅱ總是建立一個空閑任務,這個任務在沒有其它任務進入就緒態(tài)時投入運行。這個空閑任務[OSTaskIdle()]永遠設為最低優(yōu)先級,即OS_LOWEST_PRI0??臻e任務OSTaskIdle()什么也不做,只是在不停地給一個32位的名叫OSIdleCtr的計數(shù)器加1,統(tǒng)計任務(見3.08節(jié),統(tǒng)計任務)使用這個計數(shù)器以確定現(xiàn)行應用軟件實際消耗的CPU時間。程序清單L3.11是空閑任務的代碼。在計數(shù)器加1前后,中斷是先關掉再開啟的,因為8位以及大多數(shù)16位微處理器的32位加1需要多條指令,要防止高優(yōu)先級的任務或中斷服務子程序從中打入。空閑任務不可能被應用軟件刪除。

程序清單L3.11μC/OS-Ⅱ的空閑任務.

voidOSTaskIdle(void*pdata)

{

pdata=pdata;

for(;;){

OS_ENTER_CRITICAL();

OSIdleCtr++;

OS_EXIT_CRITICAL();

}

}

3.8 統(tǒng)計任務

μC/OS-Ⅱ有一個提供運行時間統(tǒng)計的任務。這個任務叫做OSTaskStat(),如果用戶將系統(tǒng)定義常數(shù)OS_TASK_STAT_EN(見文件OS_CFG.H)設為1,這個任務就會建立。一旦得到了允許,OSTaskStat()每秒鐘運行一次(見文件OS_CORE.C),計算當前的CPU利用率。換句話說,OSTaskStat()告訴用戶應用程序使用了多少CPU時間,用百分比表示,這個值放在一個有符號8位整數(shù)OSCPUsage中,精讀度是1個百分點。

如果用戶應用程序打算使用統(tǒng)計任務,用戶必須在初始化時建立一個唯一的任務,在這個任務中調用OSStatInit()(見文件OS_CORE.C)。換句話說,在調用系統(tǒng)啟動函數(shù)OSStart()之前,用戶初始代碼必須先建立一個任務,在這個任務中調用系統(tǒng)統(tǒng)計初始化函數(shù)OSStatInit(),然后再建立應用程序中的其它任務。程序清單L3.12是統(tǒng)計任務的示意性代碼。

程序清單L3.12初始化統(tǒng)計任務.

voidmain(void)

{

OSInit();/* 初始化uC/OS-II(1)*/

/* 安裝uC/OS-II的任務切換向量 */

/* 創(chuàng)建用戶起始任務(為了方便討論,這里以TaskStart()作為起始任務)(2)*/

OSStart();/* 開始多任務調度 (3)*/

}

voidTaskStart(void*pdata)

{

/* 安裝并啟動uC/OS-II的時鐘節(jié)拍 (4)*/

OSStatInit();/* 初始化統(tǒng)計任務 (5)*/

/* 創(chuàng)建用戶應用程序任務 */

for(;;){

/* 這里是TaskStart()的代碼!*/

}

}

因為用戶的應用程序必須先建立一個起始任務[TaskStart()],當主程序main()調用系統(tǒng)啟動函數(shù)OSStcnt()的時候,μC/OS-Ⅱ只有3個要管理的任務:TaskStart()、OSTaskIdle()和OSTaskStat()。請注意,任務TaskStart()的名稱是無所謂的,叫什么名字都可以。因為μC/OS-Ⅱ已經(jīng)將空閑任務的優(yōu)先級設為最低,即OS_LOWEST_PR10,統(tǒng)計任務的優(yōu)先級設為次低,OS_LOWEST_PR10-1。啟動任務TaskStart()總是優(yōu)先級最高的任務。

圖F3.4解釋初始化統(tǒng)計任務時的流程。用戶必須首先調用的是μC/OS-Ⅱ中的系統(tǒng)初始化函數(shù)OSInit(),該函數(shù)初始化μC/OS-Ⅱ[圖F3.4(2)]。有的處理器(例如Motorola的MC68HC11),不需要“設置”中斷向量,中斷向量已經(jīng)在ROM中有了。用戶必須調用OSTaskCreat()或者OSTaskCreatExt()以建立TaskStart()[圖F3.4(3)]。進入多任務的條件準備好了以后,調用系統(tǒng)啟動函數(shù)OSStart()。這個函數(shù)將使 TaskStart()開始執(zhí)行,因為TaskStart()是優(yōu)先級最高的任務[圖F3.4(4)]]。[!--empirenews.page--]

圖F3.4統(tǒng)計任務的初始化

TaskStart()負責初始化和啟動時鐘節(jié)拍[圖F3.4(5)]。在這里啟動時鐘節(jié)拍是必要的,因為用戶不會希望在多任務還沒有開始時就接收到時鐘節(jié)拍中斷。接下去TaskStart()調用統(tǒng)計初始化函數(shù)OSStatInit()[圖F3.4(6)]。統(tǒng)計初始化函數(shù)OSStatInit()決定在沒有其它應用任務運行時,空閑計數(shù)器(OSIdleCtr)的計數(shù)有多快。奔騰II微處理器以333MHz運行時,加1操作可以使該計數(shù)器的值達到每秒15,000,000次。OSIdleCtr的值離32位計數(shù)器的溢出極限值4,294,967,296還差得遠。微處理器越來越快,用戶要注意這里可能會是將來的一個潛在問題。

系統(tǒng)統(tǒng)計初始化任務函數(shù)OSStatInit()調用延遲函數(shù)OSTimeDly()將自身延時2個時鐘節(jié)拍以停止自身的運行[圖F3.4(7)]。這是為了使OSStatInit()與時鐘節(jié)拍同步。μC/OS-Ⅱ然后選下一個優(yōu)先級最高的進入就緒態(tài)的任務運行,這恰好是統(tǒng)計任務OSTaskStat()。

讀者會在后面讀到OSTaskStat()的代碼,但粗看一下,OSTaskStat()所要做的第一件事就是查看統(tǒng)計任務就緒標志是否為“假”,如果是的話,也要延時兩個時鐘節(jié)拍[圖F3.4(8)]。一定會是這樣,因為標志OSStatRdy已被OSInit()函數(shù)初始化為“假”,所以

實際上DSTaskStat也將自己推入休眠態(tài)(Sleep)兩個時鐘節(jié)拍[圖F3.4(9)]。于是任務切換到空閑任務,OSTaskIdle()開始運行,這是唯一一個就緒態(tài)任務了。CPU處在空閑任務OSTaskIdle中,直到TaskStart()的延遲兩個時鐘節(jié)拍完成[圖3.4(10)]。兩個時鐘節(jié)拍之后,TaskStart()恢復運行[圖F3.4(11)]。 在執(zhí)行OSStartInit()時,空閑計數(shù)器OSIdleCtr被清零[圖F3.4(12)]。然后,OSStatInit()將自身延時整整一秒[圖F3.4(13)]。因為沒有其它進入就緒態(tài)的任務,OSTaskIdle()又獲得了CPU的控制權[圖F3.4(14)]。一秒鐘以后,TaskStart()繼續(xù)運行,還是在OSStatInit()中,空閑計數(shù)器將1秒鐘內計數(shù)的值存入空閑計數(shù)器最大值OSIdleCtrMax中[圖F3.4(15)]。

OSStarInit()將統(tǒng)計任務就緒標志OSStatRdy設為“真”[圖F3.4(16)],以此來允許兩個時鐘節(jié)拍以后OSTaskStat()開始計算CPU的利用率。

統(tǒng)計任務的初始化函數(shù)OSStatInit()的代碼如程序清單L3.13所示。

程序清單L3.13統(tǒng)計任務的初始化.

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)計任務OSStat()的代碼程序清單L3.14所示。在前面一段中,已經(jīng)討論了為什么要等待統(tǒng)計任務就緒標志OSStatRdy[L3.14(1)]。這個任務每秒執(zhí)行一次,以確定所有應用程序中的任務消耗了多少CPU時間。當用戶的應用程序代碼加入以后,運行空閑任務的CPU時間就少了,OSIdleCtr就不會像原來什么任務都不運行時有那么多計數(shù)。要知道,OSIdleCtr的最大計數(shù)值是OSStatInit()在初始化時保存在計數(shù)器最大值OSIdleCtrMax中的。CPU利用率(表達式[3.1])是保存在變量OSCPUsage[L3.14(2)]中的:

[3.1]表達式 Needtotypesettheequation.

一旦上述計算完成,OSTaskStat()調用任務統(tǒng)計外界接入函數(shù)OSTaskStatHook()[L3.14(3)],這是一個用戶可定義的函數(shù),這個函數(shù)能使統(tǒng)計任務得到擴展。這樣,用戶可以計算并顯示所有任務總的執(zhí)行時間,每個任務執(zhí)行時間的百分比以及其它信息(參見1.09節(jié)例3)。

程序清單L3.14統(tǒng)計任務

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中,中斷服務子程序要用匯編語言來寫。然而,如果用戶使用的C語言編譯器支持在線匯編語言的話,用戶可以直接將中斷服務子程序代碼放在C語言的程序文件中。中斷服務子程序的示意碼如程序清單L3.15所示。

程序清單L3.15μC/OS-II中的中斷服務子程序.

用戶中斷服務子程序:

保存全部CPU寄存器;(1)

調用OSIntEnter或OSIntNesting直接加1; (2)

執(zhí)行用戶代碼做中斷服務;(3)

調用OSIntExit(); (4)

執(zhí)行中斷返回指令; (6)

用戶代碼應該將全部CPU寄存器推入當前任務棧[L3.15(1)]。注意,有些微處理器,例如Motorola68020(及68020以上的微處理器),做中斷服務時使用另外的堆棧。

μC/OS-Ⅱ可以用在這類微處理器中,當任務切換時,寄存器是保存在被中斷了的那個任務的棧中的。

μC/OS-Ⅱ需要知道用戶在做中斷服務,故用戶應該調用OSIntEnter(),或者將全程變量OSIntNesting[L3.15(2)]直接加1,如果用戶使用的微處理器有存儲器直接加1的單條指令的話。如果用戶使用的微處理器沒有這樣的指令,必須先將OSIntNesting讀入寄存器,再將寄存器加1,然后再寫回到變量OSIatNesting中去,就不如調用OSIatEnter()。

OSIntNesting是共享資源。OSIntEnter()把上述三條指令用開中斷、關中斷保護起來,以保證處理OSIntNesting時的排它性。直接給OSIntNesting加1比調用OSIntEnter()快得多,可能時,直接加1更好。要當心的是,在有些情況下,從OSIntEnter()返回時,會把中斷開了。遇到這種情況,在調用OSIntEnter()之前要先清中斷源,否則,中斷將連續(xù)反復打入,用戶應用程序就會崩潰![!--empirenews.page--]

上述兩步完成以后,用戶可以開始服務于叫中斷的設備了[L3.15(3)]。這一段完全取決于應用。μC/OS-Ⅱ允許中斷嵌套,因為μC/OS-Ⅱ跟蹤嵌套層數(shù)OSIntNesting。然而,為允許中斷嵌套,在多數(shù)情況下,用戶應在開中斷之前先清中斷源。

調用脫離中斷函數(shù)OSIntExit()[L3.15(4)]標志著中斷服務子程序的終結,OSIntExit()將中斷嵌套層數(shù)計數(shù)器減1。當嵌套計數(shù)器減到零時,所有中斷,包括嵌套的中斷就都完成了,此時μC/OS-Ⅱ要判定有沒有優(yōu)先級較高的任務被中斷服務子程序(或任一嵌套的中斷)喚醒了。如果有優(yōu)先級高的任務進入了就緒態(tài),μC/OS-Ⅱ就返回到那個高優(yōu)先級的任務,OSIntExit()返回到調用點[L3.15(5)]。保存的寄存器的值是在這時恢復的,然后是執(zhí)行中斷返回指令[L3.16(6)]。注意,如果調度被禁止了(OSIntNesting>0),μC/OS-Ⅱ將被返回到被中斷了的任務。

以上描述的詳細解釋如圖F3.5所示。中斷來到了[F3.5(1)]但還不能被被CPU識別,也許是因為中斷被μC/OS-Ⅱ或用戶應用程序關了,或者是因為CPU還沒執(zhí)行完當前指令。一旦CPU響應了這個中斷[F3.5(2)],CPU的中斷向量(至少大多數(shù)微處理器是如此)跳轉到中斷服務子程序[F3.5(3)]。如上所述,中斷服務子程序保存CPU寄存器(也叫做CPUcontext)[F3.5(4)],一旦做完,用戶中斷服務子程序通知μC/OS-Ⅱ進入中斷服務子程序了,辦法是調用OSIntEnter()或者給SIntNesting直接加1[F3.5(5)]。然后用戶中斷服務代碼開始執(zhí)行[F3.5(6)]。用戶中斷服務中做的事要盡可能地少,要把大部分工作留給任務去做。中斷服務子程序通知某任務去做事的手段是調用以下函數(shù)之一:OSMboxPost(),OSQPost(),OSQPostFront(),OSSemPost()。中斷發(fā)生并由上述函數(shù)發(fā)出消息時,接收消息的任務可能是,也可能不是掛起在郵箱、隊列或信號量上的任務。用戶中斷服務完成以后,要調用OSIntExit()[F3.5(7)]。從時序圖上可以看出,對被中斷了的任務說來,如果沒有高優(yōu)先級的任務被中斷服務子程序激活而進入就緒態(tài),OSIntExit()只占用很短的運行

時間。進而,在這種情況下,CPU寄存器只是簡單地恢復[F3.5(8)]并執(zhí)行中斷返回指令

[F3.5(9)]。如果中斷服務子程序使一個高優(yōu)先級的任務進入了就緒態(tài),則OSIntExit()將占用

較長的運行時間,因為這時要做任務切換[F3.5(10)]。新任務的寄存器內容要恢復并執(zhí)行中

斷返回指令[F3.5(12)]。

圖 3.5 中斷服務

進入中斷函數(shù) OSIntEnter()的代碼如程序清單 L3.16 所示,從中斷服務中退出函數(shù)

OSIntExit()的代碼如程序清單 L3.17 所示。如前所述,OSIntEnter()所做的事是非常少的。

程序清單 L3.16 通知μC/OS-Ⅱ,中斷服務子程序開始了.

voidOSIntEnter(void)

{

OS_ENTER_CRITICAL();

OSIntNesting++;

OS_EXIT_CRITICAL();

}

恢復所有CPU寄存器; (5)

程序清單 L3.17 通知μC/OS-Ⅱ,脫離了中斷服務

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)]而調度函數(shù)OSSched()的調度條件是:中斷嵌套層數(shù)計數(shù)器和鎖定

嵌套計數(shù)器(OSLockNesting)二者都必須是零。第二個不同點是,OSRdyTbl[]所需的檢索

值Y是保存在全程變量OSIntExitY中的[L3.17(3)]。這是為了避免在任務棧中安排局部變

量。這個變量在哪兒和中斷任務切換函數(shù)OSIntCtxSw()有關,(見9.04.03節(jié),中斷任務

切換函數(shù))。最后一點,如果需要做任務切換,OSIntExit()將調用OSIntCtxSw()[L3.17

(4)]而不是調用OS_TASK_SW(),正像在OSSched()函數(shù)中那樣。

調用中斷切換函數(shù)OSIntCtxSw()而不調用任務切換函數(shù) OS_TASK_SW(),有兩個原因,

首先是,如程序清單中L3.5(1)和圖F3.6(1)所示,一半的工作,即CPU寄存器入棧的工作

已經(jīng)做完了。第二個原因是,在中斷服務子程序中調用OSIntExit()時,將返回地址推入

了堆棧[L3.15(4)和F3.6(2)]。OSIntExit()中的進入臨界段函數(shù)OS_ENTER_CRITICAL()或

許將CPU的狀態(tài)字也推入了堆棧L3.7(1)和F3.6(3)。這取決于中斷是怎么被關掉的(見第8

章移植μC/OS-Ⅱ)。最后,調用OSIntCtxSw()時的返回地址又被推入了堆棧[L3.17(4)和

F3.1(4)],除了棧中不相關的部分,當任務掛起時,棧結構應該與μC/OS-Ⅱ所規(guī)定的完全

一致。OSIntCtxSw()只需要對棧指針做簡單的調整,如圖F3.6(5)所示。換句話說,調整

棧結構要保證所有掛起任務的棧結構看起來是一樣的。

圖3.6中斷中的任務切換函數(shù)OSIntCtxSw()調整棧結構

有的微處理器,像Motorola68HC11中斷發(fā)生時CPU寄存器是自動入棧的,且要想允許

中斷嵌套的話,在中斷服務子程序中要重新開中斷,這可以視作一個優(yōu)點。確實,如果用

戶中斷服務子程序執(zhí)行得非???,用戶不需要通知任務自身進入了中斷服務,只要不在中

斷服務期間開中斷,也不需要調用OSIntEnter()或OSIntNesting加1。程序清單L3。18

中的示意代碼表示這種情況。一個任務和這個中斷服務子程序通訊的唯一方法是通過全程

變量。

[!--empirenews.page--]

程序清單 L3.18Motorola68HC11中的中斷服務子程序

M68HC11_ISR:/* 快中斷服務程序,必須禁止中斷*/

所有寄存器被CPU自動保存;

執(zhí)行用戶代碼以響應中斷;

執(zhí)行中斷返回指令;

3.10 時鐘節(jié)拍

μC/OS需要用戶提供周期性信號源,用于實現(xiàn)時間延時和確認超時。節(jié)拍率應在每秒

10次到100次之間,或者說10到100Hz。時鐘節(jié)拍率越高,系統(tǒng)的額外負荷就越重。時鐘

節(jié)拍的實際頻率取決于用戶應用程序的精度。時鐘節(jié)拍源可以是專門的硬件定時器,也可

以是來自50/60Hz交流電源的信號。

用戶必須在多任務系統(tǒng)啟動以后再開啟時鐘節(jié)拍器,也就是在調用OSStart()之后。

換句話說,在調用OSStart()之后做的第一件事是初始化定時器中斷。通常,容易犯的錯

誤是將允許時鐘節(jié)拍器中斷放在系統(tǒng)初始化函數(shù)OSInit()之后,在調啟動多任務系統(tǒng)啟動

函數(shù)OSStart()之前,如程序清單L3.19所示。

程序清單L3.19啟動時鐘就節(jié)拍器的不正確做法.

voidmain(void)

{

.

.

OSInit();/* 初始化uC/OS-II*/

.

.

/* 應用程序初始化代碼 ...*/

/*... 通過調用OSTaskCreate()創(chuàng)建至少一個任務 */

.

.

允許時鐘節(jié)拍(TICKER)中斷;/* 千萬不要在這里允許時鐘節(jié)拍中斷!!! */

.

.

OSStart();/* 開始多任務調度 */

}

這里潛在地危險是,時鐘節(jié)拍中斷有可能在μC/OS-Ⅱ啟動第一個任務之前發(fā)生,此時μC/OS-Ⅱ是處在一種不確定的狀態(tài)之中,用戶應用程序有可能會崩潰。

μC/OS-Ⅱ中的時鐘節(jié)拍服務是通過在中斷服務子程序中調用OSTimeTick()實現(xiàn)的。

時鐘節(jié)拍中斷服從所有前面章節(jié)中描述的規(guī)則。時鐘節(jié)拍中斷服務子程序的示意代碼如程序清單L3.20所示。這段代碼必須用匯編語言編寫,因為在C語言里不能直接處理CPU的寄存器。

程序清單L3.20時鐘節(jié)拍中斷服務子程序的示意代碼

voidOSTickISR(void)

{

保存處理器寄存器的值;

調用OSIntEnter()或是將OSIntNesting加1;

調用OSTimeTick();

調用OSIntExit();

恢復處理器寄存器的值;

執(zhí)行中斷返回指令;

}

時鐘節(jié)拍函數(shù)OSTimeTick()的代碼如程序清單3.21所示。OSTimtick()以調用可由用戶定義的時鐘節(jié)拍外連函數(shù)OSTimTickHook()開始,這個外連函數(shù)可以將時鐘節(jié)拍函數(shù)OSTimtick()予以擴展[L3.2(1)]。筆者決定首先調用OSTimTickHook()是打算在時鐘節(jié)拍中斷服務一開始就給用戶一個可以做點兒什么的機會,因為用戶可能會有一些時間要求苛刻的工作要做。OSTimtick()中量大的工作是給每個用戶任務控制塊OS_TCB中的時間延時項OSTCBDly減1(如果該項不為零的話)。OSTimTick()從OSTCBList開始,沿著OS_TCB鏈表做,一直做到空閑任務[L3.21(3)]。當某任務的任務控制塊中的時間延時項OSTCBDly減到了零,這個任務就進入了就緒態(tài)[L3.21(5)]。而確切被任務掛起的函數(shù)OSTaskSuspend()掛起的任務則不會進入就緒態(tài)[L3.21(4)]。OSTimTick()的執(zhí)行時間直接與應用程序中建立了多少個任務成正比。

程序清單L3.21時鐘節(jié)拍函數(shù)OSTimtick()的一個節(jié)拍服務

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()還通過調用OSTime()[L3.21(7)]累加從開機以來的時間,用的是一個無符號32位變量。注意,在給OSTime加1之前使用了關中斷,因為多數(shù)微處理器給32位數(shù)加1的操作都得使用多條指令。

中斷服務子程序似乎就得寫這么長,如果用戶不喜歡將中斷服務程序寫這么長,可以從任務級調用OSTimeTick(),如程序清單L3.22所示。要想這么做,得建立一個高于應用程序中所有其它任務優(yōu)先級的任務。時鐘節(jié)拍中斷服務子程序利用信號量或郵箱發(fā)信號給這個高優(yōu)先級的任務。

程序清單L3.22時鐘節(jié)拍任務TickTask()作時鐘節(jié)拍服務.

voidTickTask(void*pdata)

{

pdata=pdata;

for(;;){

OSMboxPend(...);/* 等待從時鐘節(jié)拍中斷服務程序發(fā)來的信號 */

OSTimeTick();

}

}

用戶當然需要先建立一個郵箱(初始化成NULL)用于發(fā)信號給上述任何告知時鐘節(jié)拍中斷已經(jīng)發(fā)生了(程序清單L3.23)。

程序清單L3.23時鐘節(jié)拍中斷服務函數(shù)OSTickISR()做節(jié)拍服務。

voidOSTickISR(void)

{

保存處理器寄存器的值;

調用OSIntEnter()或是將OSIntNesting加1;

發(fā)送一個‘空’消息(例如, (void*)1)到時鐘節(jié)拍的郵箱;

調用OSIntExit();

恢復處理器寄存器的值;

執(zhí)行中斷返回指令;

}

3.11 μC/OS-Ⅱ初始化

在調用μC/OS-Ⅱ的任何其它服務之前,μC/OS-Ⅱ要求用戶首先調用系統(tǒng)初始化函數(shù)OSIint()。OSIint()初始化μC/OS-Ⅱ所有的變量和數(shù)據(jù)結構(見OS_CORE.C)。

OSInit()建立空閑任務idle task,這個任務總是處于就緒態(tài)的??臻e任務OSTaskIdle()的優(yōu)先級總是設成最低,即OS_LOWEST_PRIO。如果統(tǒng)計任務允許OS_TASK_STAT_EN和任務建立擴展允許都設為1,則OSInit()還得建立統(tǒng)計任務OSTaskStat()并且讓其進入就緒態(tài)。OSTaskStat的優(yōu)先級總是設為OS_LOWEST_PRIO-1。

圖F3.7表示調用OSInit()之后,一些μC/OS-Ⅱ變量和數(shù)據(jù)結構之間的關系。其解釋是基于以下假設的:[!--empirenews.page--]

z 在文件OS_CFG.H中,OS_TASK_STAT_EN是設為1的。

z 在文件OS_CFG.H中,OS_LOWEST_PRIO是設為63的。

z 在文件OS_CFG.H中,最多任務數(shù)OS_MAX_TASKS是設成大于2的。

以上兩個任務的任務控制塊(OS_TCBs)是用雙向鏈表鏈接在一起的。OSTCBList指向這個鏈表的起始處。當建立一個任務時,這個任務總是被放在這個鏈表的起始處。換句話說,OSTCBList總是指向最后建立的那個任務。鏈的終點指向空字符NULL(也就是零)。

因為這兩個任務都處在就緒態(tài),在就緒任務表OSRdyTbl[]中的相應位是設為1的。還有,因為這兩個任務的相應位是在OSRdyTbl[]的同一行上,即屬同一組,故OSRdyGrp中只有1位是設為1的。

μC/OS-Ⅱ還初始化了4個空數(shù)據(jù)結構緩沖區(qū),如圖F3.8所示。每個緩沖區(qū)都是單向鏈

表,允許μC/OS-Ⅱ從緩沖區(qū)中迅速得到或釋放一個緩沖區(qū)中的元素。注意,空任務控制塊

在空緩沖區(qū)中的數(shù)目取決于最多任務數(shù)OS_MAX_TASKS,這個最多任務數(shù)是在OS_CFG.H文件

中定義的。μC/OS-Ⅱ自動安排總的系統(tǒng)任務數(shù)OS_N_SYS_TASKS(見文件μC/OS-Ⅱ.H)???/p>

制塊OS_TCB的數(shù)目也就自動確定了。當然,包括足夠的任務控制塊分配給統(tǒng)計任務和空閑

任務。指向空事件表OSEventFreeList和空隊列表OSFreeList的指針將在第6章,任務間

通訊與同步中討論。指向空存儲區(qū)的指針表OSMemFreeList將在第7章存儲管理中討論。

3.12 μC/OS-Ⅱ的啟動

多任務的啟動是用戶通過調用 OSStart()實現(xiàn)的。然而,啟動μC/OS-Ⅱ之前,用戶至少

要建立一個應用任務,如程序清單L3.24所示。

程序清單L3.24初始化和啟動μC/OS-Ⅱ

voidmain(void)

{

OSInit();/* 初始化uC/OS-II*/

.

.

通過調用OSTaskCreate()或OSTaskCreateExt()創(chuàng)建至少一個任務;

.

.

OSStart();/* 開始多任務調度!OSStart()永遠不會返回 */

}

圖3.7調用OSInit()之后的數(shù)據(jù)結構

圖3.8空緩沖區(qū)

OSStart()的代碼如程序清單L3.25所示。當調用OSStart()時,OSStart()從任務就緒表中找出那個用戶建立的優(yōu)先級最高任務的任務控制塊[L3.25(1)]。然后,OSStart()調用高優(yōu)先級就緒任務啟動函數(shù) OSStartHighRdy()[L3,25(2)],(見匯編語言文件 OS_CPU_A.ASM),這個文件與選擇的微處理器有關。實質上,函數(shù)OSStartHighRdy()是將任務棧中保存的值彈回到 CPU 寄存器中,然后執(zhí)行一條中斷返回指令,中斷返回指令強制執(zhí)行該任務代碼。見9.04.01節(jié),高優(yōu)先級就緒任務啟動函數(shù) OSStartHighRdy()。那一節(jié)詳細介紹對于 80x86微處理器是怎么做的。注意,OSStartHighRdy()將永遠不返回到 OSStart()。

程序清單 L3.25 啟動多任務.

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)

}

}

多任務啟動以后變量與數(shù)據(jù)結構中的內容如圖 F3.9 所示。這里筆者假設用戶建立的任務優(yōu)先級為 6,注意,OSTaskCtr指出已經(jīng)建立了 3 個任務。OSRunning已設為“真” ,指出多任務已經(jīng)開始, OSPrioCur和 OSPrioHighRdy存放的是用戶應用任務的優(yōu)先級, OSTCBCur和 OSTCBHighRdy二者都指向用戶任務的任務控制塊。

3.13 獲取當前μC/OS-Ⅱ的版本號

應用程序調用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調用OSStart()以后的變量與數(shù)據(jù)結構

3.14 OSEvent()函數(shù)

讀者或許注意到有4個OS_CORE.C中的函數(shù)沒有在本章中提到。這4個函數(shù)是

OSEventWaitListInit(),OSEventTaskRdy(),OSEventTaskWait(),OSEventTO()。這幾個

函數(shù)是放在文件OS_CORE.C中的,而對如何使用這個函數(shù)的解釋見第6章,任務間的通訊與

同步。

本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內容真實性等。需要轉載請聯(lián)系該專欄作者,如若文章內容侵犯您的權益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或將催生出更大的獨角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉型技術解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關鍵字: 汽車 人工智能 智能驅動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務中斷的風險,如企業(yè)系統(tǒng)復雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務連續(xù)性,提升韌性,成...

關鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質量流程IT總裁陶景文發(fā)表了演講。

關鍵字: 華為 12nm EDA 半導體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權最終是由生態(tài)的繁榮決定的。

關鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務引領增長 以科技創(chuàng)新為引領,提升企業(yè)核心競爭力 堅持高質量發(fā)展策略,塑強核心競爭優(yōu)勢...

關鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術學會聯(lián)合牽頭組建的NVI技術創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術創(chuàng)新聯(lián)...

關鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關鍵字: BSP 信息技術
關閉
關閉