新聞詳情
嵌入式開發(fā)內(nèi)核結(jié)構(gòu)瀏覽數(shù):17次
TOC \o "1-3" \h \z 第3章內(nèi)核結(jié)構(gòu)1 3.3任務控制塊(Task Control Blocks, OS_TCBs)3 3.5任務調(diào)度(Task Scheduling)8 3.6給調(diào)度器上鎖和開鎖(Locking and UnLocking the Scheduler)9 本章給出μC/OS-Ⅱ的主要結(jié)構(gòu)概貌。讀者將學習以下一些內(nèi)容; ?μC/OS-Ⅱ是怎樣處理臨界段代碼的; ?什么是任務,怎樣把用戶的任務交給μC/OS-Ⅱ; ?任務是怎樣調(diào)度的; ?應用程序CPU的利用率是多少,μC/OS-Ⅱ是怎樣知道的; ?怎樣寫中斷服務子程序; ?什么是時鐘節(jié)拍,μC/OS-Ⅱ是怎樣處理時鐘節(jié)拍的; ?μC/OS-Ⅱ是怎樣初始化的,以及 ?怎樣啟動多任務; 本章還描述以下函數(shù),這些服務于應用程序: ?OS_ENTER_CRITICAL() 和 OS_EXIT_CRITICAL(), ?OSInit(), ?OSStart(), ?OSIntEnter() 和 OSIntExit(), ?OSSchedLock() 和 OSSchedUnlock(), 以及 ?OSVersion(). 和其它內(nèi)核一樣,μC/OS-Ⅱ為了處理臨界段代碼需要關中斷,處理完畢后再開中斷。這使得μC/OS-Ⅱ能夠避免同時有其它任務或中斷服務進入臨界段代碼。關中斷的時間是實時內(nèi)核開發(fā)商應提供的最重要的指標之一,因為這個指標影響用戶系統(tǒng)對實時事件的響應性。μC/OS-Ⅱ努力使關中斷時間降至最短,但就使用μC/OS-Ⅱ而言,關中斷的時間很大程度上取決于微處理器的架構(gòu)以及編譯器所生成的代碼質(zhì)量。 微處理器一般都有關中斷/開中斷指令,用戶使用的C語言編譯器必須有某種機制能夠在C中直接實現(xiàn)關中斷/開中斷地操作。某些C編譯器允許在用戶的C源代碼中插入?yún)R編語言的語句。這使得插入微處理器指令來關中斷/開中斷很容易實現(xiàn)。而有的編譯器把從C語言中關中斷/開中斷放在語言的擴展部分。μC/OS-Ⅱ定義兩個宏(macros)來關中斷和開中斷,以便避開不同C編譯器廠商選擇不同的方法來處理關中斷和開中斷。μC/OS-Ⅱ中的這兩個宏調(diào)用分別是:OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()。因為這兩個宏的定義取決于所用的微處理器,故在文件OS_CPU.H中可以找到相應宏定義。每種微處理器都有自己的OS_CPU.H文件。 4. 一個任務通常是一個無限的循環(huán)[L3.1(2)],如程序清單3.1所示。一個任務看起來像其它C的函數(shù)一樣,有函數(shù)返回類型,有形式參數(shù)變量,但是任務是絕不會返回的。故返回參數(shù)必須定義成void[L3.1(1)]。
不同的是,當任務完成以后,任務可以自我刪除,如清單L3.2所示。注意任務代碼并非真的刪除了,μC/OS-Ⅱ只是簡單地不再理會這個任務了,這個任務的代碼也不會再運行,如果任務調(diào)用了OSTaskDel(),這個任務絕不會返回什么。
形式參數(shù)變量[L3.1(1)]是由用戶代碼在第一次執(zhí)行的時候帶入的。請注意,該變量的類型是一個指向void的指針。這是為了允許用戶應用程序傳遞任何類型的數(shù)據(jù)給任務。這個指針好比一輛萬能的車子,如果需要的話,可以運載一個變量的地址,或一個結(jié)構(gòu),甚至是一個函數(shù)的地址。也可以建立許多相同的任務,所有任務都使用同一個函數(shù)(或者說是同一個任務代碼程序), 見第一章的例1。例如,用戶可以將四個串行口安排成每個串行口都是一個單獨的任務,而每個任務的代碼實際上是相同的。并不需要將代碼復制四次,用戶可以建立一個任務,向這個任務傳入一個指向某數(shù)據(jù)結(jié)構(gòu)的指針變量,這個數(shù)據(jù)結(jié)構(gòu)定義串行口的參數(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ù)語句#define constant定義的。因此用戶可以有多達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號)也被一些內(nèi)核服務函數(shù)調(diào)用,如改變優(yōu)先級函數(shù)OSTaskChangePrio(),以及任務刪除函數(shù)OSTaskDel()。 為了使μC/OS-Ⅱ能管理用戶任務,用戶必須在建立一個任務的時候,將任務的起始地址與其它參數(shù)一起傳給下面兩個函數(shù)中的一個:OSTastCreat或OSTaskCreatExt()。OSTaskCreateExt()是OSTaskCreate()的擴展,擴展了一些附加的功能。,這兩個函數(shù)的解釋見第四章,任務管理。 圖3.1是μC/OS-Ⅱ控制下的任務狀態(tài)轉(zhuǎn)換圖。在任一給定的時刻,任務的狀態(tài)一定是在這五種狀態(tài)之一。 睡眠態(tài)(DORMANT)指任務駐留在程序空間之中,還沒有交給μC/OS-Ⅱ管理,(見程序清單L3.1或L3.2)。把任務交給μC/OS-Ⅱ是通過調(diào)用下述兩個函數(shù)之一:OSTaskCreate()或OSTaskCreateExt()。當任務一旦建立,這個任務就進入就緒態(tài)準備運行。任務的建立可以是在多任務運行開始之前,也可以是動態(tài)地被一個運行著的任務建立。如果一個任務是被另一個任務建立的,而這個任務的優(yōu)先級高于建立它的那個任務,則這個剛剛建立的任務將立即得到CPU的控制權(quán)。一個任務可以通過調(diào)用OSTaskDel()返回到睡眠態(tài),或通過調(diào)用該函數(shù)讓另一個任務進入睡眠態(tài)。 調(diào)用OSStart()可以啟動多任務。OSStart()函數(shù)運行進入就緒態(tài)的優(yōu)先級最高的任務。就緒的任務只有當所有優(yōu)先級高于這個任務的任務轉(zhuǎn)為等待狀態(tài),或者是被刪除了,才能進入運行態(tài)。 圖3.1 任務的狀態(tài) 正在運行的任務可以通過調(diào)用兩個函數(shù)之一將自身延遲一段時間,這兩個函數(shù)是OSTimeDly()或OSTimeDlyHMSM()。這個任務于是進入等待狀態(tài),等待這段時間過去,下一個優(yōu)先級最高的、并進入了就緒態(tài)的任務立刻被賦予了CPU的控制權(quán)。等待的時間過去以后,系統(tǒng)服務函數(shù)OSTimeTick()使延遲了的任務進入就緒態(tài)(見3.10節(jié),時鐘節(jié)拍)。 正在運行的任務期待某一事件的發(fā)生時也要等待,手段是調(diào)用以下3個函數(shù)之一:OSSemPend(),OSMboxPend(),或OSQPend()。調(diào)用后任務進入了等待狀態(tài)(WAITING)。當任務因等待事件被掛起(Pend),下一個優(yōu)先級最高的任務立即得到了CPU的控制權(quán)。當事件發(fā)生了,被掛起的任務進入就緒態(tài)。事件發(fā)生的報告可能來自另一個任務,也可能來自中斷服務子程序。 正在運行的任務是可以被中斷的,除非該任務將中斷關了,或者μC/OS-Ⅱ?qū)⒅袛嚓P了。被中斷了的任務就進入了中斷服務態(tài)(ISR)。響應中斷時,正在執(zhí)行的任務被掛起,中斷服務子程序控制了CPU的使用權(quán)。中斷服務子程序可能會報告一個或多個事件的發(fā)生,而使一個或多個任務進入就緒態(tài)。在這種情況下,從中斷服務子程序返回之前,μC/OS-Ⅱ要判定,被中斷的任務是否還是就緒態(tài)任務中優(yōu)先級最高的。如果中斷服務子程序使一個優(yōu)先級更高的任務進入了就緒態(tài),則新進入就緒態(tài)的這個優(yōu)先級更高的任務將得以運行,否則原來被中斷了的任務才能繼續(xù)運行。 當所有的任務都在等待事件發(fā)生或等待延遲時間結(jié)束,μC/OS-Ⅱ執(zhí)行空閑任務(idle task),執(zhí)行OSTaskIdle()函數(shù)。 4. 一旦任務建立了,任務控制塊OS_TCBs將被賦值(程序清單3.3)。任務控制塊是一個數(shù)據(jù)結(jié)構(gòu),當任務的CPU使用權(quán)被剝奪時,μC/OS-Ⅱ用它來保存該任務的狀態(tài)。當任務重新得到CPU使用權(quán)時,任務控制塊能確保任務從當時被中斷的那一點絲毫不差地繼續(xù)執(zhí)行。OS_TCBs全部駐留在RAM中。讀者將會注意到筆者在組織這個數(shù)據(jù)結(jié)構(gòu)時,考慮到了各成員的邏輯分組。任務建立的時候,OS_TCBs就被初始化了(見第四章 任務管理)。
.OSTCBStkPtr是指向當前任務棧頂?shù)闹羔槨&藽/OS-Ⅱ允許每個任務有自己的棧,尤為重要的是,每個任務的棧的容量可以是任意的。有些商業(yè)內(nèi)核要求所有任務棧的容量都一樣,除非用戶寫一個復雜的接口函數(shù)來改變之。這種限制浪費了RAM,當各任務需要的棧空間不同時,也得按任務中預期棧容量需求最多的來分配棧空間。OSTCBStkPtr是OS_TCB數(shù)據(jù)結(jié)構(gòu)中唯一的一個能用匯編語言來處置的變量(在任務切換段的代碼Context-switching code之中,)把OSTCBStkPtr放在數(shù)據(jù)結(jié)構(gòu)的最前面,使得從匯編語言中處理這個變量時較為容易。 .OSTCBExtPtr 指向用戶定義的任務控制塊擴展。用戶可以擴展任務控制塊而不必修改μC/OS-Ⅱ的源代碼。.OSTCBExtPtr只在函數(shù)OstaskCreateExt()中使用,故使用時要將OS_TASK_CREAT_EN設為1,以允許建立任務函數(shù)的擴展。例如用戶可以建立一個數(shù)據(jù)結(jié)構(gòu),這個數(shù)據(jù)結(jié)構(gòu)包含每個任務的名字,或跟蹤某個任務的執(zhí)行時間,或者跟蹤切換到某個任務的次數(shù)(見例3)。注意,筆者將這個擴展指針變量放在緊跟著堆棧指針的位置,為的是當用戶需要在匯編語言中處理這個變量時,從數(shù)據(jù)結(jié)構(gòu)的頭上算偏移量比較方便。 .OSTCBStkBottom是指向任務棧底的指針。如果微處理器的棧指針是遞減的,即棧存儲器從高地址向低地址方向分配,則OSTCBStkBottom指向任務使用的棧空間的最低地址。類似地,如果微處理器的棧是從低地址向高地址遞增型的,則OSTCBStkBottom指向任務可以使用的棧空間的最高地址。函數(shù)OSTaskStkChk()要用到變量OSTCBStkBottom,在運行中檢驗棧空間的使用情況。用戶可以用它來確定任務實際需要的棧空間。這個功能只有當用戶在任務建立時允許使用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()中要調(diào)用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,而后又建立、刪除了任務,棧檢驗功能報告的棧使用情況將是錯誤的。如果任務一旦建立就決不會被刪除,而用戶初始化時,已將RAM清過零,則OS_TASK_OPT_STK_CLR不需要再定義,這可以節(jié)約程序執(zhí)行時間。傳遞了OS_TASK_OPT_STK_CLR將增加TaskCreateExt()函數(shù)的執(zhí)行時間,因為要將棧空間清零。棧容量越大,清零花的時間越長。最后一個選擇項OS_TASK_OPT_SAVE_FP通知TaskCreateExt(),任務要做浮點運算。如果微處理器有硬件的浮點協(xié)處理器,則所建立的任務在做任務調(diào)度切換時,浮點寄存器的內(nèi)容要保存。 .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)。可以給.OSTCBStat賦其它的值,在文件uCOS_II.H中有關于這個值的描述。 .OSTCBPrio是任務優(yōu)先級。高優(yōu)先級任務的.OSTCBPrio值小。也就是說,這個值越小,任務的優(yōu)先級越高。 .OSTCBX, .OSTCBY, .OSTCBBitX和 .OSTCBBitY用于加速任務進入就緒態(tài)的過程或進入等待事件發(fā)生狀態(tài)的過程(避免在運行中去計算這些值)。這些值是在任務建立時算好的,或者是在改變?nèi)蝿諆?yōu)先級時算出的。這些值的算法見程序清單L3.4。
.OSTCBDelReq是一個布爾量,用于表示該任務是否需要刪除,用法將在后面的章節(jié)中描述(見第4章 任務管理) 應用程序中可以有的最多任務數(shù)(OS_MAX_TASKS)是在文件OS_CFG.H中定義的。這個最多任務數(shù)也是μC/OS-Ⅱ分配給用戶程序的最多任務控制塊OS_TCBs的數(shù)目。將OS_MAX_TASKS的數(shù)目設置為用戶應用程序?qū)嶋H需要的任務數(shù)可以減小RAM的需求量。所有的任務控制塊OS_TCBs都是放在任務控制塊列表數(shù)組OSTCBTbl[]中的。請注意,μC/OS-Ⅱ分配給系統(tǒng)任務OS_N_SYS_TASKS若干個任務控制塊,見文件μC/OS-Ⅱ.H,供其內(nèi)部使用。目前,一個用于空閑任務,另一個用于任務統(tǒng)計(如果OS_TASK_STAT_EN是設為1的)。在μC/OS-Ⅱ初始化的時候,如圖3.2所示,所有任務控制塊OS_TCBs被鏈接成單向空任務鏈表。當任務一旦建立,空任務控制塊指針OSTCBFreeList指向的任務控制塊便賦給了該任務,然后OSTCBFreeList的值調(diào)整為指向下鏈表中下一個空的任務控制塊。一旦任務被刪除,任務控制塊就還給空任務鏈表。 圖3.2 空任務列表 每個任務被賦予不同的優(yōu)先級等級,從0級到最低優(yōu)先級OS_LOWEST_PR1O,包括0和OS_LOWEST_PR1O在內(nèi)(見文件OS_CFG.H)。當μC/OS-Ⅱ初始化的時候,最低優(yōu)先級OS_LOWEST_PR1O總是被賦給空閑任務idle task。注意,最多任務數(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-Ⅱ?qū)AM(數(shù)據(jù)空間)的需求量。 為確定下次該哪個優(yōu)先級的任務運行了,內(nèi)核調(diào)度器總是將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)先級。
讀者可以看出,任務優(yōu)先級的低三位用于確定任務在總就緒表OSRdyTbl[]中的所在位。接下去的三位用于確定是在OSRdyTbl[]數(shù)組的第幾個元素。OSMapTbl[]是在ROM中的(見文件OS_CORE.C)屏蔽字,用于限制OSRdyTbl[]數(shù)組的元素下標在0到7之間,見表3.1 圖3.3μC/OS-Ⅱ就緒表 如果一個任務被刪除了,則用程序清單3.6中的代碼做求反處理。
以上代碼將就緒任務表數(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所示。
例如,如果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的工作就完成了。 μC/OS-Ⅱ總是運行進入就緒態(tài)任務中優(yōu)先級最高的那一個。確定哪個任務優(yōu)先級最高,下面該哪個任務運行了的工作是由調(diào)度器(Scheduler)完成的。任務級的調(diào)度是由函數(shù)OSSched()完成的。中斷級的調(diào)度是由另一個函數(shù)OSIntExt()完成的,這個函數(shù)將在以后描述。OSSched()的代碼如程序清單L3.8所示。
μC/OS-Ⅱ任務調(diào)度所花的時間是常數(shù),與應用程序中建立的任務數(shù)無關。如程序清單中[L3.8(1)]條件語句的條件不滿足,任務調(diào)度函數(shù)OSSched()將退出,不做任務調(diào)度。這個條件是:如果在中斷服務子程序中調(diào)用OSSched(),此時中斷嵌套層數(shù)OSIntNesting>0,或者由于用戶至少調(diào)用了一次給任務調(diào)度上鎖函數(shù)OSSchedLock(),使OSLockNesting>0。如果不是在中斷服務子程序調(diào)用OSSched(),并且任務調(diào)度是允許的,即沒有上鎖,則任務調(diào)度函數(shù)將找出那個進入就緒態(tài)且優(yōu)先級最高的任務[L3.8(2)],進入就緒態(tài)的任務在就緒任務表中有相應的位置位。一旦找到那個優(yōu)先級最高的任務,OSSched()檢驗這個優(yōu)先級最高的任務是不是當前正在運行的任務,以此來避免不必要的任務調(diào)度[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)]。最后宏調(diào)用OS_TASK_SW()來完成實際上的任務切換[L3.8(6)]。 任務切換很簡單,由以下兩步完成,將被掛起任務的微處理器寄存器推入堆棧,然后將較高優(yōu)先級的任務的寄存器值從棧中恢復到寄存器中。在μC/OS-Ⅱ中,就緒任務的棧結(jié)構(gòu)總是看起來跟剛剛發(fā)生過中斷一樣,所有微處理器的寄存器都保存在棧中。換句話說,μC/OS-Ⅱ運行就緒態(tài)的任務所要做的一切,只是恢復所有的CPU寄存器并運行中斷返回指令。為了做任務切換,運行OS_TASK_SW(),人為模仿了一次中斷。多數(shù)微處理器有軟中斷指令或者陷阱指令TRAP來實現(xiàn)上述操作。中斷服務子程序或陷阱處理(Trap hardler),也稱作事故處理(exception handler),必須提供中斷向量給匯編語言函數(shù)OSCtxSw()。OSCtxSw()除了需要OS_TCBHighRdy指向即將被掛起的任務,還需要讓當前任務控制塊OSTCBCur指向即將被掛起的任務,參見第8章,移植μC/OS-Ⅱ,有關于OSCtxSw()的更詳盡的解釋。 OSSched()的所有代碼都屬臨界段代碼。在尋找進入就緒態(tài)的優(yōu)先級最高的任務過程中,為防止中斷服務子程序把一個或幾個任務的就緒位置位,中斷是被關掉的。為縮短切換時間,OSSched()全部代碼都可以用匯編語言寫。為增加可讀性,可移植性和將匯編語言代碼最少化,OSSched()是用C寫的。 4. 給調(diào)度器上鎖函數(shù)OSSchedlock()(程序清單L3.9)用于禁止任務調(diào)度,直到任務完成后調(diào)用給調(diào)度器開鎖函數(shù)OSSchedUnlock()為止,(程序清單L3.10)。調(diào)用OSSchedlock()的任務保持對CPU的控制權(quán),盡管有個優(yōu)先級更高的任務進入了就緒態(tài)。然而,此時中斷是可以被識別的,中斷服務也能得到(假設中斷是開著的)。OSSchedlock()和OSSchedUnlock()必須成對使用。變量OSLockNesting跟蹤OSSchedLock()函數(shù)被調(diào)用的次數(shù),以允許嵌套的函數(shù)包含臨界段代碼,這段代碼其它任務不得干預。μC/OS-Ⅱ允許嵌套深度達255層。當OSLockNesting等于零時,調(diào)度重新得到允許。函數(shù)OSSchedLock()和OSSchedUnlock()的使用要非常謹慎,因為它們影響μC/OS-Ⅱ?qū)θ蝿盏恼9芾怼?/span> 當OSLockNesting減到零的時候,OSSchedUnlock()調(diào)用OSSched[L3.10(2)]。OSSchedUnlock()是被某任務調(diào)用的,在調(diào)度器上鎖的期間,可能有什么事件發(fā)生了并使一個更高優(yōu)先級的任務進入就緒態(tài)。 調(diào)用OSSchedLock()以后,用戶的應用程序不得使用任何能將現(xiàn)行任務掛起的系統(tǒng)調(diào)用。也就是說,用戶程序不得調(diào)用OSMboxPend()、OSQPend()、OSSemPend()、OSTaskSuspend(OS_PR1O_SELF)、OSTimeDly()或OSTimeDlyHMSM(),直到OSLockNesting回零為止。因為調(diào)度器上了鎖,用戶就鎖住了系統(tǒng),任何其它任務都不能運行。 當?shù)蛢?yōu)先級的任務要發(fā)消息給多任務的郵箱、消息隊列、信號量時(見第6章 任務間通訊和同步),用戶不希望高優(yōu)先級的任務在郵箱、隊列和信號量沒有得到消息之前就取得了CPU的控制權(quán),此時,用戶可以使用禁止調(diào)度器函數(shù)。
μC/OS-Ⅱ總是建立一個空閑任務,這個任務在沒有其它任務進入就緒態(tài)時投入運行。這個空閑任務[OSTaskIdle()]永遠設為最低優(yōu)先級,即OS_LOWEST_PRI0。空閑任務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)先級的任務或中斷服務子程序從中打入。空閑任務不可能被應用軟件刪除。
μ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)計任務,用戶必須在初始化時建立一個唯一的任務,在這個任務中調(diào)用OSStatInit()(見文件OS_CORE.C)。換句話說,在調(diào)用系統(tǒng)啟動函數(shù)OSStart()之前,用戶初始代碼必須先建立一個任務,在這個任務中調(diào)用系統(tǒng)統(tǒng)計初始化函數(shù)OSStatInit(),然后再建立應用程序中的其它任務。程序清單L3.12是統(tǒng)計任務的示意性代碼。
因為用戶的應用程序必須先建立一個起始任務[TaskStart()],當主程序main()調(diào)用系統(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)計任務時的流程。用戶必須首先調(diào)用的是μC/OS-Ⅱ中的系統(tǒng)初始化函數(shù)OSInit(),該函數(shù)初始化μC/OS-Ⅱ[圖F3.4(2)]。有的處理器(例如Motorola的MC68HC11),不需要“設置”中斷向量,中斷向量已經(jīng)在ROM中有了。用戶必須調(diào)用OSTaskCreat()或者OSTaskCreatExt()以建立TaskStart()[圖F3.4(3)]。進入多任務的條件準備好了以后,調(diào)用系統(tǒng)啟動函數(shù)OSStart()。這個函數(shù)將使TaskStart()開始執(zhí)行,因為TaskStart()是優(yōu)先級最高的任務[圖F3.4(4)]]。 圖F3.4統(tǒng)計任務的初始化 TaskStart()負責初始化和啟動時鐘節(jié)拍[圖F3.4(5)]。在這里啟動時鐘節(jié)拍是必要的,因為用戶不會希望在多任務還沒有開始時就接收到時鐘節(jié)拍中斷。接下去TaskStart()調(diào)用統(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()調(diào)用延遲函數(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的控制權(quán)[圖F3.4(14)]。一秒鐘以后,TaskStart()繼續(xù)運行,還是在OSStatInit()中,空閑計數(shù)器將1秒鐘內(nèi)計數(shù)的值存入空閑計數(shù)器最大值OSIdleCtrMax中[圖F3.4(15)]。 OSStarInit()將統(tǒng)計任務就緒標志OSStatRdy設為“真”[圖F3.4(16)],以此來允許兩個時鐘節(jié)拍以后OSTaskStat()開始計算CPU的利用率。 統(tǒng)計任務的初始化函數(shù)OSStatInit()的代碼如程序清單 L3.13所示。
統(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]表達式 Need to typeset the equation. 一旦上述計算完成,OSTaskStat()調(diào)用任務統(tǒng)計外界接入函數(shù)OSTaskStatHook() [L3.14(3)],這是一個用戶可定義的函數(shù),這個函數(shù)能使統(tǒng)計任務得到擴展。這樣,用戶可以計算并顯示所有任務總的執(zhí)行時間,每個任務執(zhí)行時間的百分比以及其它信息(參見1.09節(jié)例3)。
μC/OS中,中斷服務子程序要用匯編語言來寫。然而,如果用戶使用的C語言編譯器支持在線匯編語言的話,用戶可以直接將中斷服務子程序代碼放在C語言的程序文件中。中斷服務子程序的示意碼如程序清單L3.15所示。
用戶代碼應該將全部CPU寄存器推入當前任務棧[L3.15(1)]。注意,有些微處理器,例如Motorola68020(及68020以上的微處理器),做中斷服務時使用另外的堆棧。 μC/OS-Ⅱ可以用在這類微處理器中,當任務切換時,寄存器是保存在被中斷了的那個任務的棧中的。 μC/OS-Ⅱ需要知道用戶在做中斷服務,故用戶應該調(diào)用OSIntEnter(),或者將全程變量OSIntNesting[L3.15(2)]直接加1,如果用戶使用的微處理器有存儲器直接加1的單條指令的話。如果用戶使用的微處理器沒有這樣的指令,必須先將OSIntNesting讀入寄存器,再將寄存器加1,然后再寫回到變量OSIatNesting中去,就不如調(diào)用OSIatEnter()。OSIntNesting是共享資源。OSIntEnter()把上述三條指令用開中斷、關中斷保護起來,以保證處理OSIntNesting時的排它性。直接給OSIntNesting加1比調(diào)用OSIntEnter()快得多,可能時,直接加1更好。要當心的是,在有些情況下,從OSIntEnter()返回時,會把中斷開了。遇到這種情況,在調(diào)用OSIntEnter()之前要先清中斷源,否則,中斷將連續(xù)反復打入,用戶應用程序就會崩潰! 上述兩步完成以后,用戶可以開始服務于叫中斷的設備了[L3.15(3)]。這一段完全取決于應用。μC/OS-Ⅱ允許中斷嵌套,因為μC/OS-Ⅱ跟蹤嵌套層數(shù)OSIntNesting。然而,為允許中斷嵌套,在多數(shù)情況下,用戶應在開中斷之前先清中斷源。 調(diào)用脫離中斷函數(shù)OSIntExit()[L3.15(4)]標志著中斷服務子程序的終結(jié),OSIntExit()將中斷嵌套層數(shù)計數(shù)器減1。當嵌套計數(shù)器減到零時,所有中斷,包括嵌套的中斷就都完成了,此時μC/OS-Ⅱ要判定有沒有優(yōu)先級較高的任務被中斷服務子程序(或任一嵌套的中斷)喚醒了。如果有優(yōu)先級高的任務進入了就緒態(tài),μC/OS-Ⅱ就返回到那個高優(yōu)先級的任務,OSIntExit()返回到調(diào)用點[L3.15(5)]。保存的寄存器的值是在這時恢復的,然后是執(zhí)行中斷返回指令[L3.16(6)]。注意,如果調(diào)度被禁止了(OSIntNesting>0),μC/OS-Ⅱ?qū)⒈环祷氐奖恢袛嗔说娜蝿铡?/span> 以上描述的詳細解釋如圖F3.5所示。中斷來到了[F3.5(1)]但還不能被被CPU識別,也許是因為中斷被μC/OS-Ⅱ或用戶應用程序關了,或者是因為CPU還沒執(zhí)行完當前指令。一旦CPU響應了這個中斷[F3.5(2)],CPU的中斷向量(至少大多數(shù)微處理器是如此)跳轉(zhuǎn)到中斷服務子程序[F3.5(3)]。如上所述,中斷服務子程序保存CPU寄存器(也叫做 CPU context)[F3.5(4)],一旦做完,用戶中斷服務子程序通知μC/OS-Ⅱ進入中斷服務子程序了,辦法是調(diào)用OSIntEnter()或者給OSIntNesting直接加1[F3.5(5)]。然后用戶中斷服務代碼開始執(zhí)行[F3.5(6)]。用戶中斷服務中做的事要盡可能地少,要把大部分工作留給任務去做。中斷服務子程序通知某任務去做事的手段是調(diào)用以下函數(shù)之一:OSMboxPost(),OSQPost(),OSQPostFront(),OSSemPost()。中斷發(fā)生并由上述函數(shù)發(fā)出消息時,接收消息的任務可能是,也可能不是掛起在郵箱、隊列或信號量上的任務。用戶中斷服務完成以后,要調(diào)用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)]。新任務的寄存器內(nèi)容要恢復并執(zhí)行中斷返回指令[F3.5(12)]。 圖3.5 中斷服務 進入中斷函數(shù)OSIntEnter()的代碼如程序清單L3.16所示,從中斷服務中退出函數(shù)OSIntExit()的代碼如程序清單L3.17所示。如前所述,OSIntEnter()所做的事是非常少的。
OSIntExit()看起來非常像OSSched()。但有三點不同。第一點,OSIntExit()使中斷嵌套層數(shù)減1[L3.17(2)]而調(diào)度函數(shù)OSSched()的調(diào)度條件是:中斷嵌套層數(shù)計數(shù)器和鎖定嵌套計數(shù)器(OSLockNesting)二者都必須是零。第二個不同點是,OSRdyTbl[]所需的檢索值Y是保存在全程變量OSIntExitY中的[L3.17(3)]。這是為了避免在任務棧中安排局部變量。這個變量在哪兒和中斷任務切換函數(shù)OSIntCtxSw()有關,(見9.04.03節(jié),中斷任務切換函數(shù))。最后一點,如果需要做任務切換,OSIntExit()將調(diào)用OSIntCtxSw()[L3.17(4)]而不是調(diào)用OS_TASK_SW(),正像在OSSched()函數(shù)中那樣。 調(diào)用中斷切換函數(shù)OSIntCtxSw()而不調(diào)用任務切換函數(shù)OS_TASK_SW(),有兩個原因,首先是,如程序清單中L3.5(1)和圖F3.6(1)所示,一半的工作,即CPU寄存器入棧的工作已經(jīng)做完了。第二個原因是,在中斷服務子程序中調(diào)用OSIntExit()時,將返回地址推入了堆棧[L3.15(4)和F3.6(2)]。OSIntExit()中的進入臨界段函數(shù)OS_ENTER_CRITICAL()或許將CPU的狀態(tài)字也推入了堆棧L3.7(1)和F3.6(3)。這取決于中斷是怎么被關掉的(見第8章移植μC/OS-Ⅱ)。最后,調(diào)用OSIntCtxSw()時的返回地址又被推入了堆棧[L3.17(4)和F3.1(4)],除了棧中不相關的部分,當任務掛起時,棧結(jié)構(gòu)應該與μC/OS-Ⅱ所規(guī)定的完全一致。OSIntCtxSw()只需要對棧指針做簡單的調(diào)整,如圖F3.6(5)所示。換句話說,調(diào)整棧結(jié)構(gòu)要保證所有掛起任務的棧結(jié)構(gòu)看起來是一樣的。 圖3.6中斷中的任務切換函數(shù)OSIntCtxSw()調(diào)整棧結(jié)構(gòu) 有的微處理器,像Motorola 68HC11中斷發(fā)生時CPU寄存器是自動入棧的,且要想允許中斷嵌套的話,在中斷服務子程序中要重新開中斷,這可以視作一個優(yōu)點。確實,如果用戶中斷服務子程序執(zhí)行得非常快,用戶不需要通知任務自身進入了中斷服務,只要不在中斷服務期間開中斷,也不需要調(diào)用OSIntEnter()或OSIntNesting加1。程序清單L3。18中的示意代碼表示這種情況。一個任務和這個中斷服務子程序通訊的唯一方法是通過全程變量。
μC/OS需要用戶提供周期性信號源,用于實現(xiàn)時間延時和確認超時。節(jié)拍率應在每秒10次到100次之間,或者說10到100Hz。時鐘節(jié)拍率越高,系統(tǒng)的額外負荷就越重。時鐘節(jié)拍的實際頻率取決于用戶應用程序的精度。時鐘節(jié)拍源可以是專門的硬件定時器,也可以是來自50/60Hz交流電源的信號。 用戶必須在多任務系統(tǒng)啟動以后再開啟時鐘節(jié)拍器,也就是在調(diào)用OSStart()之后。換句話說,在調(diào)用OSStart()之后做的第一件事是初始化定時器中斷。通常,容易犯的錯誤是將允許時鐘節(jié)拍器中斷放在系統(tǒng)初始化函數(shù)OSInit()之后,在調(diào)啟動多任務系統(tǒng)啟動函數(shù)OSStart()之前,如程序清單L3.19所示。
這里潛在地危險是,時鐘節(jié)拍中斷有可能在μC/OS-Ⅱ啟動第一個任務之前發(fā)生,此時μC/OS-Ⅱ是處在一種不確定的狀態(tài)之中,用戶應用程序有可能會崩潰。 μC/OS-Ⅱ中的時鐘節(jié)拍服務是通過在中斷服務子程序中調(diào)用OSTimeTick()實現(xiàn)的。時鐘節(jié)拍中斷服從所有前面章節(jié)中描述的規(guī)則。時鐘節(jié)拍中斷服務子程序的示意代碼如程序清單L3.20所示。這段代碼必須用匯編語言編寫,因為在C語言里不能直接處理CPU的寄存器。
時鐘節(jié)拍函數(shù)OSTimeTick()的代碼如程序清單3.21所示。OSTimtick()以調(diào)用可由用戶定義的時鐘節(jié)拍外連函數(shù)OSTimTickHook()開始,這個外連函數(shù)可以將時鐘節(jié)拍函數(shù)OSTimtick()予以擴展[L3.2(1)]。筆者決定首先調(diào)用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í)行時間直接與應用程序中建立了多少個任務成正比。
OSTimeTick()還通過調(diào)用OSTime()[L3.21(7)]累加從開機以來的時間,用的是一個無符號32位變量。注意,在給OSTime加1之前使用了關中斷,因為多數(shù)微處理器給32位數(shù)加1的操作都得使用多條指令。 中斷服務子程序似乎就得寫這么長,如果用戶不喜歡將中斷服務程序?qū)戇@么長,可以從任務級調(diào)用OSTimeTick(),如程序清單L3.22所示。要想這么做,得建立一個高于應用程序中所有其它任務優(yōu)先級的任務。時鐘節(jié)拍中斷服務子程序利用信號量或郵箱發(fā)信號給這個高優(yōu)先級的任務。
用戶當然需要先建立一個郵箱(初始化成NULL)用于發(fā)信號給上述任何告知時鐘節(jié)拍中斷已經(jīng)發(fā)生了(程序清單L3.23)。
在調(diào)用μC/OS-Ⅱ的任何其它服務之前,μC/OS-Ⅱ要求用戶首先調(diào)用系統(tǒng)初始化函數(shù)OSIint()。OSIint()初始化μC/OS-Ⅱ所有的變量和數(shù)據(jù)結(jié)構(gòu)(見OS_CORE.C)。 OSInit()建立空閑任務idle task,這個任務總是處于就緒態(tài)的。空閑任務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表示調(diào)用OSInit()之后,一些μC/OS-Ⅱ變量和數(shù)據(jù)結(jié)構(gòu)之間的關系。其解釋是基于以下假設的: ?在文件OS_CFG.H中,OS_TASK_STAT_EN是設為1的。 ?在文件OS_CFG.H中,OS_LOWEST_PRIO是設為63的。 ?在文件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ù)結(jié)構(gòu)緩沖區(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)。控制塊OS_TCB的數(shù)目也就自動確定了。當然,包括足夠的任務控制塊分配給統(tǒng)計任務和空閑任務。指向空事件表OSEventFreeList和空隊列表OSFreeList的指針將在第6章,任務間通訊與同步中討論。指向空存儲區(qū)的指針表OSMemFreeList將在第7章存儲管理中討論。 多任務的啟動是用戶通過調(diào)用OSStart()實現(xiàn)的。然而,啟動μC/OS-Ⅱ之前,用戶至少要建立一個應用任務,如程序清單L3.24所示。
圖3.7 調(diào)用OSInit()之后的數(shù)據(jù)結(jié)構(gòu) 圖3.8 空緩沖區(qū) OSStart()的代碼如程序清單L3.25所示。當調(diào)用OSStart()時,OSStart()從任務就緒表中找出那個用戶建立的優(yōu)先級最高任務的任務控制塊[L3.25(1)]。然后,OSStart()調(diào)用高優(yōu)先級就緒任務啟動函數(shù)OSStartHighRdy()[L3,25(2)],(見匯編語言文件OS_CPU_A.ASM),這個文件與選擇的微處理器有關。實質(zhì)上,函數(shù)OSStartHighRdy()是將任務棧中保存的值彈回到CPU寄存器中,然后執(zhí)行一條中斷返回指令,中斷返回指令強制執(zhí)行該任務代碼。見9.04.01節(jié),高優(yōu)先級就緒任務啟動函數(shù)OSStartHighRdy()。那一節(jié)詳細介紹對于80x86微處理器是怎么做的。注意,OSStartHighRdy()將永遠不返回到OSStart()。
多任務啟動以后變量與數(shù)據(jù)結(jié)構(gòu)中的內(nèi)容如圖F3.9所示。這里筆者假設用戶建立的任務優(yōu)先級為6,注意,OSTaskCtr指出已經(jīng)建立了3個任務。OSRunning已設為“真”,指出多任務已經(jīng)開始,OSPrioCur和OSPrioHighRdy存放的是用戶應用任務的優(yōu)先級,OSTCBCur和OSTCBHighRdy二者都指向用戶任務的任務控制塊。 應用程序調(diào)用OSVersion()[程序清單L3.26]可以得到當前μC/OS-Ⅱ的版本號。OSVersion()函數(shù)返回版本號值乘以100。換言之,200表示版本號2.00。
為找到μ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) 讀者或許注意到有4個OS_CORE.C中的函數(shù)沒有在本章中提到。這4個函數(shù)是OSEventWaitListInit(),OSEventTaskRdy(),OSEventTaskWait(),OSEventTO()。這幾個函數(shù)是放在文件OS_CORE.C中的,而對如何使用這個函數(shù)的解釋見第6章,任務間的通訊與同步。 |