μC/OS-II在80x86上的移植
本章將介紹如何將μC/OS-II移植到INTEL80x86系列CPU上,本章所介紹的移植和代碼都是針對(duì)80x86的實(shí)模式的,且編譯器在大模式下編譯和連接。本章的內(nèi)容同樣適用于下述CPU:
80186
80286
80386
80486
Pentium
PentiumII
實(shí)際上,將要介紹的移植過(guò)程適用于所有與80x86兼容的CPU,如AMD,Cyrix,NEC(V-系列)等等。以INTEL的為例只是一種更典型的情況。80x86CPU每年的產(chǎn)量有數(shù)百萬(wàn),大部分用于個(gè)人計(jì)算機(jī),但用于嵌入式系統(tǒng)的數(shù)量也在不斷增加。最快的處理器(Pentium系列)將在2000年達(dá)到1G的工作頻率。
大部分支持80x86(實(shí)模式)的C編譯器都提供了不同的內(nèi)存使用模式,每一種都有不同的內(nèi)存組織方式,適用于不同規(guī)模的應(yīng)用程序。在大模式下,應(yīng)用程序和數(shù)據(jù)最大尋址空間為1Mb,程序指針為32位。下一節(jié)將介紹為什么32位指針只用到了其中的20位來(lái)尋址(1Mb)。
本章所介紹的內(nèi)容也適用于8086處理器,但由于8086沒(méi)有PUSHA指令,移植的時(shí)候要用幾條PUSH指令來(lái)代替。
圖F9.1顯示了工作在實(shí)模式下的80x86處理器的編程模式。所有的寄存器都是16位,在任務(wù)切換時(shí)需要保存寄存器內(nèi)容。
圖F9.180x86 實(shí)模式內(nèi)部寄存器圖.
80x86提供了一種特殊的機(jī)制,使得用16位寄存器可以尋址1Mb地址空間,這就是存儲(chǔ)器分段的方法。內(nèi)存的物理地址用段地址寄存器和偏移量寄存器共同表示。計(jì)算方法是:段地址寄存器的內(nèi)容左移4位(乘以16),再加上偏移量寄存器(其他6個(gè)寄存器中的一個(gè),AX,BP,SP,SI,DI或IP)的內(nèi)容,產(chǎn)生可尋址1Mb的20位物理地址。圖F9.2表明了寄存器是如何組合的。段寄存器可以指向一個(gè)內(nèi)存塊,稱(chēng)為一個(gè)段。一個(gè)16位的段寄存器可以表示65,536個(gè)不同的段,因此可以尋址1,048,576字節(jié)。由于偏移量寄存器也是16位的,所以單個(gè)段不能超過(guò)64K。實(shí)際操作中,應(yīng)用程序是由許多小于64K的段組成的。
圖F9.2 使用段寄存器和偏移量寄存器尋址.
代碼段寄存器(CS)指向當(dāng)前程序運(yùn)行的代碼段起始,堆棧段寄存器(SS)指向程序堆棧段的起始,數(shù)據(jù)段寄存器指向程序數(shù)據(jù)區(qū)的起始,附加段寄存器(ES)指向一個(gè)附加數(shù)據(jù)存儲(chǔ)區(qū)。每次CPU尋址的時(shí)候,段寄存器中的某一個(gè)會(huì)被自動(dòng)選用,加上偏移量寄存器的內(nèi)容作為物理地址。文獻(xiàn)中會(huì)經(jīng)常發(fā)現(xiàn)用段地址—偏移量表示地址的方法,例如1000:00FF表示物理地址0x100FF。
9.00 開(kāi)發(fā)工具
筆者采用的是BorlandC/C++V3.1和BorlandTurboAssembler匯編器完成程序的移植和測(cè)試,它可以產(chǎn)生可重入的代碼,同時(shí)支持在C程序中嵌入?yún)R編語(yǔ)句。編譯完成后,程序可在PC機(jī)上運(yùn)行。本書(shū)代碼的測(cè)試是在一臺(tái)Pentium-II計(jì)算機(jī)上完成的,操作系統(tǒng)是MicrosoftWindows95。實(shí)際上編譯器生成的是DOS可執(zhí)行文件,在Windows的DOS窗口中運(yùn)行。
只要您用的編譯器可以產(chǎn)生實(shí)模式下的代碼,移植工作就可以進(jìn)行。如果開(kāi)發(fā)環(huán)境不同,就只能麻煩您更改一下編譯器和匯編器的設(shè)置了。
9.01 目錄和文件
在安裝μC/OS-II的時(shí)候,安裝程序?qū)押陀布嚓P(guān)的,針對(duì)INTEL80x86的代碼安裝到SOFTWAREuCOS-IIIx86L目錄下。代碼是80x86實(shí)模式,且在編譯器大模式下編譯的。移植部
分的代碼可在下述文件中找到:OS_CPU.H,OS_CPU_C.C,和OS_CPU_A.ASM。
9.02 INCLUDES.H文件
INCLUDES.H是主頭文件,在所有后綴名為.C的文件的開(kāi)始都包含INCLUDES.H文件。使用INCLUDES.H的好處是所有的.C文件都只包含一個(gè)頭文件,程序簡(jiǎn)潔,可讀性強(qiáng)。缺點(diǎn)是.C文件
可能會(huì)包含一些它并不需要的頭文件,額外的增加編譯時(shí)間。與優(yōu)點(diǎn)相比,多一些編譯時(shí)間還
是可以接受的。用戶(hù)可以改寫(xiě)INCLUDES.H文件,增加自己的頭文件,但必須加在文件末尾。程
序清單L9.1是為80x86編寫(xiě)的INCLUDES.H文件的內(nèi)容。
程序清單L 9.1 INCLUDES.H.
#include
#include
#include
#include
#include
#include
#include
#include"softwareucos-iiix86los_cpu.h"
#include"os_cfg.h"
#include"softwareblockspcsourcepc.h"
#include"softwareucos-iisourceucos_ii.h"
9.03 OS_CPU.H文件
OS_CPU.H文件中包含與處理器相關(guān)的常量,宏和結(jié)構(gòu)體的定義。程序清單L9.2是為80x86編寫(xiě)的OS_CPU.H文件的內(nèi)容。
程序清單L 9.2 OS_CPU.H.
#ifdefOS_CPU_GLOBALS
#defineOS_CPU_EXT
#else
#defineOS_CPU_EXTextern
#endif
/*
*************************************************************************
******
* 數(shù)據(jù)類(lèi)型
*(與編譯器相關(guān)的內(nèi)容)
*************************************************************************
******
*/
typedefunsignedcharBOOLEAN;
typedefunsignedcharINT8U;/* 無(wú)符號(hào)8位數(shù) (1)*/
typedefsignedcharINT8S;/* 帶符號(hào)8位數(shù) */
typedefunsignedintINT16U;/* 無(wú)符號(hào)16位數(shù) */
typedefsignedintINT16S;/* 帶符號(hào)16位數(shù) */
typedefunsignedlongINT32U;/* 無(wú)符號(hào)32位數(shù) */
typedefsignedlongINT32S;/* 帶符號(hào)32位數(shù) */
typedeffloatFP32;/* 單精度浮點(diǎn)數(shù) */
typedefdoubleFP64;/* 雙精度浮點(diǎn)數(shù) */
typedefunsignedintOS_STK;/* 堆棧入口寬度為16位 */
#defineBYTEINT8S/* 以下定義的數(shù)據(jù)類(lèi)型是為了與uC/OSV1.xx 兼容 */[!--empirenews.page--]
#defineUBYTEINT8U/*在uC/OS-II中并沒(méi)有實(shí)際的用處 */
#defineWORDINT16S
#defineUWORDINT16U
#defineLONGINT32S
#defineULONGINT32U
/*
*************************************************************************
******
*INTEL80x86(實(shí)模式, 大模式編譯)
*
*方法 #1: 用簡(jiǎn)單指令開(kāi)關(guān)中斷。
* 注意,用方法1關(guān)閉中斷,從調(diào)用函數(shù)返回后中斷會(huì)重新打開(kāi)!
* 注意將文件OS_CPU_A.ASM中與OSIntCtxSw()相關(guān)的常量從10改到8。
*
* 方法 #2: 關(guān)中斷前保存中斷被關(guān)閉的狀態(tài).
* 注意將文件OS_CPU_A.ASM中與OSIntCtxSw()相關(guān)的常量從8改到10。
*
*
*
*************************************************************************
******
*/
#defineOS_CRITICAL_METHOD2
#ifOS_CRITICAL_METHOD==1
#defineOS_ENTER_CRITICAL()asmCLI/* 關(guān)閉中斷*/
#defineOS_EXIT_CRITICAL()asmSTI/* 打開(kāi)中斷*/
#endif
#ifOS_CRITICAL_METHOD==2
#defineOS_ENTER_CRITICAL()asm{PUSHF;CLI}/* 關(guān)閉中斷 */
#defineOS_EXIT_CRITICAL()asmPOPF/* 打開(kāi)中斷 */
#endif
/*
*************************************************************************
******
*INTEL80x86(實(shí)模式, 大模式編譯)
*************************************************************************
******
*/
#defineOS_STK_GROWTH1/* 堆棧由高地址向低地址增長(zhǎng) (3)*/
#defineuCOS0x80/* 中斷向量0x80用于任務(wù)切換 (4)*/
#defineOS_TASK_SW()asmINTuCOS(5)
/*
*************************************************************************
******
* 全局變量
*************************************************************************
******
*/
OS_CPU_EXTINT8UOSTickDOSCtr;/* 為調(diào)用DOS時(shí)鐘中斷而定義的計(jì)數(shù)器*/
(6)*/
9.03.01 數(shù)據(jù)類(lèi)型
由于不同的處理器有不同的字長(zhǎng),μC/OS-II的移植需要重新定義一系列的數(shù)據(jù)結(jié)構(gòu)。使用
BorlandC/C++編譯器,整數(shù)(int)類(lèi)型數(shù)據(jù)為16位,長(zhǎng)整形(long)為32位。為了讀者方便起見(jiàn),盡管μC/OS-II中沒(méi)有用到浮點(diǎn)類(lèi)型的數(shù),在源代碼中筆者還是提供了浮點(diǎn)類(lèi)型的定義。
由于在80x86實(shí)模式中堆棧都是按字進(jìn)行操作的,沒(méi)有字節(jié)操作,所以BorlandC/C++編譯器中堆棧數(shù)據(jù)類(lèi)型OS_STK聲明為16位。所有的堆棧都必須用OS_STK聲明。
9.03.02 代碼臨界區(qū)
與其他實(shí)時(shí)系統(tǒng)一樣,μC/OS-II在進(jìn)入系統(tǒng)臨界代碼區(qū)之前要關(guān)閉中斷,等到退出臨界區(qū)后再打開(kāi)。從而保護(hù)核心數(shù)據(jù)不被多任務(wù)環(huán)境下的其他任務(wù)或中斷破壞。BorlandC/C++支持嵌入?yún)R編語(yǔ)句,所以加入關(guān)閉/打開(kāi)中斷的語(yǔ)句是很方便的。μC/OS-II定義了兩個(gè)宏用來(lái)關(guān)閉/打開(kāi)中斷:OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()。此處,筆者為用戶(hù)提供兩種開(kāi)關(guān)中斷的方法,如下所述的方法1和方法2。作為一種測(cè)試,本書(shū)采用了方法1。當(dāng)然,您可以自由決定采用那種方法。
方法1
第一種方法,也是最簡(jiǎn)單的方法,是直接將OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()定義為處理器的關(guān)閉(CLI)和打開(kāi)(STI)中斷指令。但這種方法有一個(gè)隱患,如果在關(guān)閉中斷后調(diào)用μC/OS-II函數(shù),當(dāng)函數(shù)返回后,中斷將被打開(kāi)!嚴(yán)格意義上的關(guān)閉中斷應(yīng)該是執(zhí)行OS_ENTER_CRITICAL()后中斷始終是關(guān)閉的, 方法1顯然不滿足要求。 但方法1的最大優(yōu)點(diǎn)是簡(jiǎn)單,執(zhí)行速度快(只有一條指令),在此類(lèi)操作頻繁的時(shí)候更為突出。如果在任務(wù)中并不在意調(diào)用函數(shù)返回后是否被中斷,推薦用戶(hù)采用方法1。此時(shí)需要將OSIntCtxSw()中的常量由10改到8(見(jiàn)文件OS_CPU_A.ASM)。
方法2
執(zhí)行OS_ENTER_CRITICAL()的第二種方法是先將中斷關(guān)閉的狀態(tài)保存到堆棧中,然后關(guān)閉中斷。與之對(duì)應(yīng)的OS_EXIT_CRITICAL()的操作是從堆棧中恢復(fù)中斷狀態(tài)。采用此方法,不管用戶(hù)是在中斷關(guān)閉還是允許的情況下調(diào)用μC/OS-Ⅱ中的函數(shù),在調(diào)用過(guò)程中都不會(huì)改變中斷狀態(tài)。
如果用戶(hù)在中斷關(guān)閉的情況下調(diào)用μC/OS-Ⅱ函數(shù),其實(shí)是延長(zhǎng)了中斷響應(yīng)時(shí)間。雖然OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()可以保護(hù)代碼的臨界段。但如此用法要小心,特別
是在調(diào)用OSTimeDly()一類(lèi)函數(shù)之前關(guān)閉了中斷。 此時(shí)任務(wù)將處于延時(shí)掛起狀態(tài), 等待時(shí)鐘中斷,但此時(shí)時(shí)鐘中斷是禁止的!則系統(tǒng)可能會(huì)崩潰。很明顯,所有的PEND調(diào)用都會(huì)涉及到這個(gè)問(wèn)題,必須十分小心。所以建議用戶(hù)調(diào)用μC/OS-Ⅱ的系統(tǒng)函數(shù)之前打開(kāi)中斷。
9.03.03 堆棧增長(zhǎng)方向
80x86處理器的堆棧是由高地址向低地址方向增長(zhǎng)的, 所以常量OS_STK_GROWTH必須設(shè)置為1
[程序清單L9.2(3)]。
9.03.04 OS_TASK_SW()
在 μC/OS-II中,就緒任務(wù)的堆棧初始化應(yīng)該模擬一次中斷發(fā)生后的樣子,堆棧中應(yīng)該按進(jìn)
棧次序設(shè)置好各個(gè)寄存器的內(nèi)容。OS_TASK_SW()函數(shù)模擬一次中斷過(guò)程,在中斷返回的時(shí)候進(jìn)
行任務(wù)切換。80x86提供了256個(gè)軟中斷源可供選用,中斷服務(wù)程序(ISR)(也稱(chēng)為例外處理過(guò)
程)的入口點(diǎn)必須指向匯編函數(shù)OSCtxSw()(請(qǐng)參看文件OS_CPU_A.ASM)。
由于筆者是在PC機(jī)上測(cè)試代碼的,本章的代碼用到了中斷號(hào)128(0x80),因?yàn)榇酥袛嗵?hào)是提供給用戶(hù)使用的[程序清單L9.2(4)](PC和操作系統(tǒng)會(huì)占用一部分中斷資源—譯者注),類(lèi)似的用戶(hù)可用中斷號(hào)還有0x4B到0x5B,0x5D到0x66,或者0x68到0x6F。如果用戶(hù)用的不是PC,而是其他嵌入式系統(tǒng),如80186處理器,用戶(hù)可能有更多的中斷資源可供選用。
9.03.05 時(shí)鐘節(jié)拍的發(fā)生頻率
實(shí)時(shí)系統(tǒng)中時(shí)鐘節(jié)拍的發(fā)生頻率應(yīng)該設(shè)置為10到100Hz。通常(但不是必須的)為了方便計(jì)算設(shè)為整數(shù)。不幸的是,在PC中,系統(tǒng)缺省的時(shí)鐘節(jié)拍頻率是18.20648Hz,這對(duì)于我們的計(jì)算和設(shè)置都不方便。本章中,筆者將更改PC的時(shí)鐘節(jié)拍頻率到200Hz(間隔5ms)。一方面200Hz近似18.20648Hz的11倍,可以經(jīng)過(guò)11次延時(shí)再調(diào)用DOS中斷;另一方面,在DOS中,有些操作要求時(shí)鐘間隔為54.93ms,我們?cè)O(shè)定的間隔5ms也可以滿足要求。如果您的PC機(jī)處理器是80386,時(shí)鐘節(jié)拍最快也只能到200Hz,而如果是PentiumII處理器,則達(dá)到200Hz以上沒(méi)有問(wèn)題。[!--empirenews.page--]
在文件OS_CPU.H的末尾聲明了一個(gè)8位變量OSTickDOSCtr,將保存時(shí)鐘節(jié)拍發(fā)生的次數(shù),每發(fā)生11次,調(diào)用DOS的時(shí)鐘節(jié)拍函數(shù)一次,從而實(shí)現(xiàn)與DOS時(shí)鐘的同步。OSTickDOSCtr是專(zhuān)門(mén)為PC環(huán)境而聲明的,如果在其他非PC的系統(tǒng)中運(yùn)行μC/OS-II,就不用這種同步方法,直接設(shè)定時(shí)鐘節(jié)拍發(fā)生頻率就行了。
9.04 OS_CPU_A.ASM
μC/OS-II的移植需要用戶(hù)改寫(xiě)OS_CPU_A.ASM中的四個(gè)函數(shù):
OSStartHighRdy()
OSCtxSw()
OSIntCtxSw()
OSTickISR()
9.04.01 OSStartHighRdy()
該函數(shù)由SStart()函數(shù)調(diào)用,功能是運(yùn)行優(yōu)先級(jí)最高的就緒任務(wù),在調(diào)用OSStart()之前,用戶(hù)必須先調(diào)用OSInit(),并且已經(jīng)至少創(chuàng)建了一個(gè)任務(wù)(請(qǐng)參考OSTaskCreate()和OSTaskCreateExt()函數(shù))。OSStartHighRdy()默認(rèn)指針OSTCBHighRdy指向優(yōu)先級(jí)最高就緒任務(wù)的任務(wù)控制塊(OS_TCB)(在這之前OSTCBHighRdy已由OSStart()設(shè)置好了)。圖F9.3給出了由函數(shù)OSTaskCreate()或OSTaskCreateExt()創(chuàng)建的任務(wù)的堆棧結(jié)構(gòu)。很明顯,OSTCBHighRdy-
>OSTCBStkPtr指向的是任務(wù)堆棧的頂端。
函數(shù)OSStartHighRdy()的代碼見(jiàn)程序清單L9.3。
圖F9.3 任務(wù)創(chuàng)立時(shí)的80x86堆棧結(jié)構(gòu).
為了啟動(dòng)任務(wù),OSStartHighRdy()從任務(wù)控制塊(OS_TCB)[程序清單L9.3(1)]中找到指向堆棧的指針,然后運(yùn)行POPDS[程序清單L9.3(2)],POPES[程序清單L9.3(3)],POPA[程序清單L9.3(4)],和IRET[程序清單L9.3(5)]指令。此處筆者將任務(wù)堆棧指針保存在任務(wù)控制塊的開(kāi)頭,這樣使得堆棧指針的存取在匯編語(yǔ)言中更容易操作。
當(dāng)執(zhí)行了IRET指令后,CPU會(huì)從(SS:SP)指向的堆棧中恢復(fù)各個(gè)寄存器的值并執(zhí)行中斷前的指令。SS:SP+4指向傳遞給任務(wù)的參數(shù)pdata。
程序清單L 9.3 OSStartHighRdy().
_OSStartHighRdyPROCFAR
MOVAX,SEG_OSTCBHighRdy; 載入 DS
MOVDS,AX;
LESBX,DWORDPTRDS:_OSTCBHighRdy;SS:SP=OSTCBHighRdy-
>OSTCBStkPtr (1)
MOVSS,ES:[BX+2];
MOVSP,ES:[BX+0];
;
POPDS; 恢復(fù)任務(wù)環(huán)境 (2)
POPES;(3)
POPA;(4)
;
IRET; 運(yùn)行任務(wù) (5)
_OSStartHighRdyENDP
9.04.02 OSCtxSw()
OSCtxSw()是一個(gè)任務(wù)級(jí)的任務(wù)切換函數(shù)(在任務(wù)中調(diào)用,區(qū)別于在中斷程序中調(diào)用的
OSIntCtxSw())。在80x86系統(tǒng)上,它通過(guò)執(zhí)行一條軟中斷的指令來(lái)實(shí)現(xiàn)任務(wù)切換。軟中斷向量
指向OSCtxSw()。在μC/OS-II中,如果任務(wù)調(diào)用了某個(gè)函數(shù),而該函數(shù)的執(zhí)行結(jié)果可能造成系統(tǒng)
任務(wù)重新調(diào)度(例如試圖喚醒了一個(gè)優(yōu)先級(jí)更高的任務(wù)),則在函數(shù)的末尾會(huì)調(diào)用OSSched(),
如果OSSched()判斷需要進(jìn)行任務(wù)調(diào)度,會(huì)找到該任務(wù)控制塊OS_TCB的地址,并將該地址拷貝到
OSTCBHighRdy,然后通過(guò)宏OS_TASK_SW()執(zhí)行軟中斷進(jìn)行任務(wù)切換。注意到在此過(guò)程中,變量
OSTCBCur始終包含一個(gè)指向當(dāng)前運(yùn)行任務(wù)OS_TCB的指針。程序清單L9.4為OSCtxSw()的代碼。
圖F9.4是任務(wù)被掛起或被喚醒時(shí)的堆棧結(jié)構(gòu)。在80x86處理器上,任務(wù)調(diào)用OS_TASK_SW()執(zhí)
行軟中斷指令后[圖F9.4/程序清單L9.4(1)],先向堆棧中壓入返回地址(段地址和偏移量),
然后是狀態(tài)字寄存器SW。緊接著用PUSHA[圖F9.4/程序清單L9.4(2)],PUSHES[圖F9.4/程序
清單L9.4(3)],和PUSHDS[圖F9.4/程序清單L9.4(4)]保存任務(wù)運(yùn)行環(huán)境。最后用OSCtxSw()在
任務(wù)OS_TCB中保存SS和SP寄存器。
任務(wù)環(huán)境保存完后,將調(diào)用用戶(hù)定義的對(duì)外接口函數(shù)OSTaskSwHook()[程序清單L9.4(6)]。
請(qǐng)注意,此時(shí)OSTCBCur指向當(dāng)前任務(wù)OS_TCB,OSTCBHighRdy指向新任務(wù)的OS_TCB。在
OSTaskSwHook()中,用戶(hù)可以訪問(wèn)這兩個(gè)任務(wù)的OS_TCB。如果不使用對(duì)外接口函數(shù),請(qǐng)?jiān)陬^文
件中把相應(yīng)的開(kāi)關(guān)選項(xiàng)關(guān)閉,加快任務(wù)切換的速度。
程序清單L9.4 OSCtxSw().
_OSCtxSwPROCFAR(1)
;
PUSHA; 保存當(dāng)前任務(wù)環(huán)境 (2)
PUSHES (3)
PUSHDS (4)
;
MOVAX,SEG_OSTCBCur; 載入DS
MOVDS,AX
;
LESBX,DWORDPTRDS:_OSTCBCur;OSTCBCur->OSTCBStkPtr=SS:S(5)
MOVES:[BX+2],SS
MOVES:[BX+0],SP
;
CALLFARPTR_OSTaskSwHook(6)
;
MOVAX,WORDPTRDS:_OSTCBHighRdy+2;OSTCBCur=OSTCBHighRdy(7)
MOVDX,WORDPTRDS:_OSTCBHighRdy
MOVWORDPTRDS:_OSTCBCur+2,AX
MOVWORDPTRDS:_OSTCBCur,DX
;
MOVAL,BYTEPTRDS:_OSPrioHighRdy;OSPrioCur=OSPrioHighRdy(8)
MOVBYTEPTRDS:_OSPrioCur,AL
;
LESBX,DWORDPTRDS:_OSTCBHighRdy;SS:SP=OSTCBHighRdy-
>OSTCBStkPtr (9)
MOVSS,ES:[BX+2]
MOVSP,ES:[BX]
;
POPDS; 載入新任務(wù)的CPU環(huán)境 (10)
POPES (11)
POPA (12)
;
IRET; 返回新任務(wù) (13)
;
_OSCtxSwENDP
從對(duì)外接口函數(shù)OSTaskSwHook()返回后,由于任務(wù)的更替,變量OSTCBHighRdy被拷貝到
OSTCBCur中[程序清單L9.4(7)],同樣,OSPrioHighRdy被拷貝到OSPrioCur中[程序清單
L9.4(8)]。OSCtxSw()將載入新任務(wù)的CPU環(huán)境,首先從新任務(wù)OS_TCB中取出SS和SP寄存器的值
[圖F9.4(6)/程序清單L9.4(9)],然后運(yùn)行POPDS[圖F9.4(7)/程序清單L9.4(10)],POPES
[圖F9.4(8)/程序清單L9.4(11)],POPA[圖F9.4(9)/程序清單L9.4(12)]取出其他寄存器的值,
最后用中斷返回指令I(lǐng)RET[圖F9.4(10)/L9.4(13)]完成任務(wù)切換。
需要注意的是在運(yùn)行OSCtxSw()和OSTaskSwHook()函數(shù)期間,中斷是禁止的。
9.04.03 OSIntCtxSw()
在μC/OS-II中,由于中斷的產(chǎn)生可能會(huì)引起任務(wù)切換,在中斷服務(wù)程序的最后會(huì)調(diào)用[!--empirenews.page--]
OSIntExit()函數(shù)檢查任務(wù)就緒狀態(tài),如果需要進(jìn)行任務(wù)切換,將調(diào)用OSIntCtxSw()。所以
OSIntCtxSw()又稱(chēng)為中斷級(jí)的任務(wù)切換函數(shù)。由于在調(diào)用OSIntCtxSw()之前已經(jīng)發(fā)生了中斷,
OSIntCtxSw()將默認(rèn)CPU寄存器已經(jīng)保存在被中斷任務(wù)的堆棧中了。
圖F 9.4 任務(wù)級(jí)任務(wù)切換時(shí)的80x86堆棧結(jié)構(gòu).
程序清單L9.5給出的代碼大部分與OSCtxSw()的代碼相同,不同之處是,第一,由于中斷已
經(jīng)發(fā)生, 此處不需要再保存CPU寄存器 (沒(méi)有PUSHA,PUSHES,或PUSHDS) ; 第二, OSIntCtxSw()需要調(diào)整堆棧指針,去掉堆棧中一些不需要的內(nèi)容,以使堆棧中只包含任務(wù)的運(yùn)行環(huán)境。圖F9.5可以幫助讀者理解這一過(guò)程。
程序清單L 9.5 OSIntCtxSw().
_OSIntCtxSwPROCFAR
;;IgnorecallstoOSIntExitandOSIntCtxSw
;ADDSP,8;(UncommentifOS_CRITICAL_METHODis1,seeOS_CPU.H)(1)
ADDSP,10;(UncommentifOS_CRITICAL_METHODis2,seeOS_CPU.H)
;
MOVAX,SEG_OSTCBCur; 載入DS
MOVDS,AX
;
LESBX,DWORDPTRDS:_OSTCBCur;OSTCBCur->OSTCBStkPtr=SS:SP(2)
MOVES:[BX+2],SS
MOVES:[BX+0],SP
;
CALLFARPTR_OSTaskSwHook(3)
;
MOVAX,WORDPTRDS:_OSTCBHighRdy+2;OSTCBCur=OSTCBHighRdy(4)
MOVDX,WORDPTRDS:_OSTCBHighRdy
MOVWORDPTRDS:_OSTCBCur+2,AX
MOVWORDPTRDS:_OSTCBCur,DX
;
MOVAL,BYTEPTRDS:_OSPrioHighRdy;OSPrioCur=OSPrioHighRdy(5)
MOVBYTEPTRDS:_OSPrioCur,AL
;
LESBX,DWORDPTRDS:_OSTCBHighRdy;SS:SP=OSTCBHighRdy-
>OSTCBStkPtr (6)
MOVSS,ES:[BX+2]
MOVSP,ES:[BX]
;
POPDS; 載入新任務(wù)的CPU環(huán)境 (7)
POPES (8)
POPA (9)
;
IRET; 返回新任務(wù) (10)
;
_OSIntCtxSwENDP
圖F 9.5 中斷級(jí)任務(wù)切換時(shí)的80x86堆棧結(jié)構(gòu)
當(dāng)中斷發(fā)生后,CPU在完成當(dāng)前指令后,進(jìn)入中斷處理過(guò)程。首先是保存現(xiàn)場(chǎng),將返回地址
壓入當(dāng)前任務(wù)堆棧,然后保存狀態(tài)寄存器的內(nèi)容。接下來(lái)CPU從中斷向量處找到中斷服務(wù)程序的
入口地址,運(yùn)行中斷服務(wù)程序。在μC/OS-II中,要求用戶(hù)的中斷服務(wù)程序在開(kāi)頭保存CPU其他寄
存器的內(nèi)容[圖F9.5(1)]。此后,用戶(hù)必須調(diào)用OSIntEnter()或著把全局變量OSIntNesting加1。
此時(shí),被中斷任務(wù)的堆棧中保存了任務(wù)的全部運(yùn)行環(huán)境。在中斷服務(wù)程序中,有可能引起任務(wù)
就緒狀態(tài)的改變而需要任務(wù)切換,例如調(diào)用了OSMboxPost(),OSQPostFront(),OSQPost(),或試
圖喚醒一個(gè)優(yōu)先級(jí)更高的任務(wù)(調(diào)用OSTaskResume()),還可能調(diào)用OSTimeTick(),
OSTimeDlyResume()等等。
μC/OS-II要求用戶(hù)在中斷服務(wù)程序的末尾調(diào)用OSInt Exit(),以檢查任務(wù)就緒狀態(tài)。在調(diào)用
OSInt Exit()后,返回地址會(huì)壓入堆棧中[圖F9.5(2)]。
進(jìn)入OSIntExit()后,由于要訪問(wèn)臨界代碼區(qū),首先關(guān)閉中斷。由于OS_ENTER_CRITICAL()可
能有不同的操作(見(jiàn)9.03.02節(jié)),狀態(tài)寄存器SW的內(nèi)容有可能被壓入堆棧[圖F9.5(3)]。如果
確實(shí)要進(jìn)行任務(wù)切換,指針OSTCBHighRdy將指向新的就緒任務(wù)的OS_TCB,OSIntExit()會(huì)調(diào)用
OSIntCtxSw()完成任務(wù)切換。注意,調(diào)用OSIntCtxSw()會(huì)在再一次在堆棧中保存返回地址[圖
F9.5(4)]。在進(jìn)行任務(wù)切換的時(shí)候,我們希望堆棧中只保留一次中斷發(fā)生的任務(wù)環(huán)境(如圖
F9.5(1)),而忽略掉由于函數(shù)嵌套調(diào)用而壓入的一系列返回地址(圖F9.5(2),(3),(4))。忽
略的方法也很簡(jiǎn)單,只要把堆棧指針加一個(gè)固定的值就可以了[圖F9.5(5)/程序清單L9.5(1)]。
如果用方法2實(shí)現(xiàn)OS_ENTER_CRITICAL(),這個(gè)固定值是10;如果用方法1,則是8。實(shí)際操作中
還與編譯器以及編譯模式有關(guān)。例如,有些編譯器會(huì)為OSIntExit()在堆棧中分配臨時(shí)變量,這
都會(huì)影響具體占用堆棧的大小,這一點(diǎn)需要提醒用戶(hù)注意。
一但堆棧指針重新定位后,就被保存到將要被掛起的任務(wù)OS_TCB中[圖F9.5(6)/程序清單
L9.5(2)]。在μC/OS-II中(包括μC/OS),OSIntCtxSw()是唯一一個(gè)與編譯器相關(guān)的函數(shù),也是
用戶(hù)問(wèn)的最多的。如果您的系統(tǒng)移植后運(yùn)行一段時(shí)間后就會(huì)死機(jī),就應(yīng)該懷疑是OSIntCtxSw()
中堆棧指針重新定位的問(wèn)題。
當(dāng)當(dāng)前任務(wù)的現(xiàn)場(chǎng)保存完畢后,用戶(hù)定義的對(duì)外接口函數(shù)OSTaskSwHook()會(huì)被調(diào)用[程序清
單L9.5(3)]。注意到OSTCBCur指向當(dāng)前任務(wù)的OS_TCB,OSTCBHighRdy指向新任務(wù)的OS_TCB。在
函數(shù)OSTaskSwHook()中用戶(hù)可以訪問(wèn)這兩個(gè)任務(wù)的OS_TCB。如果不用對(duì)外接口函數(shù),請(qǐng)?jiān)陬^文
件中關(guān)閉相應(yīng)的開(kāi)關(guān)選項(xiàng),提高任務(wù)切換的速度。
從對(duì)外接口函數(shù)OSTaskSwHook()返回后,由于任務(wù)的更替,變量OSTCBHighRdy被拷貝到
OSTCBCur中[程序清單L9.5(4)],同樣,OSPrioHighRdy被拷貝到OSPrioCur中[程序清單
L9.5(5)]。此時(shí),OSIntCtxSw()將載入新任務(wù)的CPU環(huán)境,首先從新任務(wù)OS_TCB中取出SS和SP寄
存器的值[圖F9.5(7)/程序清單L9.5(6)],然后運(yùn)行POPDS[圖F9.5(8)/程序清單L9.5(7)],
POPES[圖F9.5(9)/程序清單L9.5(8)],POPA[圖F9.5(10)/程序清單L9.5(9)]取出其他寄存器
的值,最后用中斷返回指令I(lǐng)RET[圖F9.5(11)/程序清單L9.5(10)]完成任務(wù)切換。
需要注意的是在運(yùn)行OSIntCtxSw()和用戶(hù)定義的OSTaskSwHook()函數(shù)期間,中斷是禁止的。
9.04.04 OSTickISR()
在9.03.05節(jié)中,我們已經(jīng)提到過(guò)實(shí)時(shí)系統(tǒng)中時(shí)鐘節(jié)拍發(fā)生頻率的問(wèn)題,應(yīng)該在10到100Hz[!--empirenews.page--]
之間。但由于PC環(huán)境的特殊性,時(shí)鐘節(jié)拍由硬件產(chǎn)生,間隔54.93ms(18.20648Hz)。我們將時(shí)
鐘節(jié)拍頻率設(shè)為200Hz。PC時(shí)鐘節(jié)拍的中斷向量為0x08,μC/OS-II將此向量截取,指向了μC/OS
的中斷服務(wù)函數(shù)OSTickISR(),而原先的中斷向量保存在中斷129(0x81)中。為滿足DOS的需要,
原先的中斷服務(wù)還是每隔54.93ms(實(shí)際上還要短些)調(diào)用一次。圖F9.6為安裝μC/OS-II前后的
中斷向量表。
在μC/OS-II中, 當(dāng)調(diào)用OSStart()啟動(dòng)多任務(wù)環(huán)境后, 時(shí)鐘中斷的作用是非常重要的。 但在PC
環(huán)境下,啟動(dòng)μC/OS-II之前就已經(jīng)有時(shí)鐘中斷發(fā)生了,實(shí)際上我們希望在μC/OS-II初始化完成之后再發(fā)生時(shí)鐘中斷,調(diào)用OSTickISR()。與此相關(guān)的有下述過(guò)程:
PC_DOSSaveReturn()函數(shù)(參看PC.C):該函數(shù)由main()調(diào)用,任務(wù)是取得DOS下時(shí)鐘中斷向量,并將其保存在0x81中。
main()函數(shù):
設(shè)定中斷向量0x80指向任務(wù)切換函數(shù)OSCtxSw()
至少創(chuàng)立一個(gè)任務(wù)
當(dāng)初始化工作完成后調(diào)用OSStart()啟動(dòng)多任務(wù)環(huán)境
第一個(gè)運(yùn)行的任務(wù):
設(shè)定中斷向量0x08指向函數(shù)OSTickISR()
將時(shí)鐘節(jié)拍頻率從18.20648改為200Hz
圖F9.6 PC 中斷向量表(IVT).
在程序清單L9.6給出了函數(shù)OSTickISR()的偽碼。和μC/OS-II中的其他中斷服務(wù)程序一樣,OSTickISR()首先在被中斷任務(wù)堆棧中保存CPU寄存器的值,然后調(diào)用OSIntEnter()。
μC/OS-II要求在中斷服務(wù)程序開(kāi)頭調(diào)用OSIntEnter(), 其作用是將記錄中斷嵌套層數(shù)的全局
變量OSIntNesting加1。如果不調(diào)用OSIntEnter(),直接將OSIntNesting加1也是允許的。接下來(lái)計(jì)數(shù)器OSTickDOSCtr減1[程序清單L9.6(3)],每發(fā)生11次中斷,OSTickDOSCtr減到0,則調(diào)用DOS的時(shí)鐘中斷處理函數(shù)[程序清單L9.6(4)],調(diào)用間隔大約是54.93ms。如果不調(diào)用DOS時(shí)鐘中斷函數(shù),則向中斷優(yōu)先級(jí)控制器(PIC)發(fā)送命令清除中斷標(biāo)志。如果調(diào)用了DOS中斷,則此項(xiàng)操作可免,因?yàn)樵贒OS的中斷程序中已經(jīng)完成了。隨后,OSTickISR()調(diào)用OSTimeTick(),檢查所有處于延時(shí)等待狀態(tài)的任務(wù),判斷是否有延時(shí)結(jié)束就緒的任務(wù)[程序清單L9.6(6)]。 在OSTickISR()的最后調(diào)用OSIntExit(), 如果在中斷中 (或其他嵌套的中斷)有更高優(yōu)先級(jí)的任務(wù)就緒,并且當(dāng)前中斷為中斷嵌套的最后一層。OSIntExit()將進(jìn)行任務(wù)調(diào)度。注意如果進(jìn)行了任務(wù)調(diào)度,OSIntExit()將不再返回調(diào)用者,而是用新任務(wù)的堆棧中的寄存器數(shù)值恢復(fù)CPU現(xiàn)場(chǎng),然后用IRET實(shí)現(xiàn)任務(wù)切換。如果當(dāng)前中斷不是中斷嵌套的最后一層,或中斷中沒(méi)有改變?nèi)蝿?wù)的就緒狀態(tài),OSIntExit()將返回調(diào)用者OSTickISR(),最后OSTickISR()返回被中斷的任務(wù)。
程序清單L9.7給出了OSTickISR()的完整代碼。
程序清單L 9.6 OSTickISR()偽碼.
voidOSTickISR(void)
{
Saveprocessorregisters;(1)
OSIntNesting++;(2)
OSTickDOSCtr—-;(3)
if(OSTickDOSCtr==0){
ChainintoDOSbyexecutingan‘INT81H‘instruction;(4)
}else{
SendEOIcommandtoPIC(PriorityInterruptController);(5)
}
OSTimeTick();(6)
OSIntExit(); (7)
Restoreprocessorregisters;(8)
Executeareturnfrominterruptinstruction(IRET);(9)
}
程序清單L9.7 OSTickISR().
_OSTickISRPROCFAR
;
PUSHA; 保存被中斷任務(wù)的CPU環(huán)境
PUSHES
PUSHDS
;
MOVAX,SEG_OSTickDOSCtr; 載入 DS
MOVDS,AX
;
INCBYTEPTR_OSIntNesting; 標(biāo)示 uC/OS-II 進(jìn)入中斷
;
DECBYTEPTRDS:_OSTickDOSCtr
CMPBYTEPTRDS:_OSTickDOSCtr,0
JNESHORT_OSTickISR1; 每11個(gè)時(shí)鐘節(jié)拍(18.206Hz)調(diào)用DOS時(shí)鐘中斷
;
MOVBYTEPTRDS:_OSTickDOSCtr,11
INT081H; 調(diào)用DOS時(shí)鐘中斷處理過(guò)程
JMPSHORT_OSTickISR2
_OSTickISR1:
MOVAL,20H; 向中斷優(yōu)先級(jí)控制器發(fā)送命令,清除標(biāo)志位.
MOVDX,20H;
OUTDX,AL;
;
_OSTickISR2:
CALLFARPTR_OSTimeTick; 調(diào)用OSTimeTick()函數(shù)
;
CALLFARPTR_OSIntExit; 標(biāo)示uC/OS-II退出中斷
;
POPDS; 恢復(fù)被中斷任務(wù)的CPU環(huán)境
POPES
POPA
;
IRET; 返回被中斷任務(wù)
;
_OSTickISRENDP
如果不更改DOS下的時(shí)鐘中斷頻率(保持18.20648Hz),OSTickISR()函數(shù)還可以簡(jiǎn)化。程序清單L9.8為18.2Hz的OSTickISR()函數(shù)的偽碼。同樣,函數(shù)開(kāi)頭要保存所有的CPU寄存器[程序清單L9.8(1)],將OSIntNesting加1[程序清單L9.8(2)]。接下來(lái)調(diào)用DOS的時(shí)鐘中斷處理過(guò)程[程序清單L9.8(3)],此處就不需要清除中斷優(yōu)先級(jí)控制器的操作了,因?yàn)镈OS的時(shí)鐘中斷處理中包含了這一過(guò)程。然后調(diào)用OSTimeTick()檢查任務(wù)的延時(shí)是否結(jié)束[程序清單L9.8(4)], 最后調(diào)用OSInt Exit()[程序清單L9.8(5)]。 結(jié)束部分是恢復(fù)CPU寄存器的內(nèi)容[程序清單L9.8(6)],執(zhí)行IRET指令返回被中斷的任務(wù)。如果采用8.2Hz的OSTickISR()函數(shù),系統(tǒng)初始化過(guò)程就不用調(diào)用PC_SetTickRate(),同時(shí)將文件OS_CFG.H中的常量OS_TICKS_PER_SEC由200改為18。
程序清單L9.9給出了18.2HzOSTickISR()的完整代碼。
程序清單L9.818.2Hz OSTickISR()偽碼.
voidOSTickISR(void)
{
Saveprocessorregisters;(1)
OSIntNesting++;(2)
ChainintoDOSbyexecutingan‘INT81H‘instruction;(3)
OSTimeTick();(4)
OSIntExit(); (5)
Restoreprocessorregisters;(6)
Executeareturnfrominterruptinstruction(IRET);(7)
}
9.05 OS_CPU_C.C
μC/OS-II的移植需要用戶(hù)改寫(xiě)OS_CPU_C.C中的六個(gè)函數(shù):[!--empirenews.page--]
OSTaskStkInit()
OSTaskCreateHook()
OSTaskDelHook()
OSTaskSwHook()
OSTaskStatHook()
OSTimeTickHook()
實(shí)際需要修改的只有OSTaskStkInit()函數(shù),其他五個(gè)函數(shù)需要聲明,但不一定有實(shí)際內(nèi)容。這五個(gè)函數(shù)都是用戶(hù)定義的,所以O(shè)S_CPU_C.C中沒(méi)有給出代碼。如果用戶(hù)需要使用這些函數(shù),請(qǐng)將文件OS_CFG.H中的#define constant OS_CPU_HOOKS_EN設(shè)為1,設(shè)為0表示不使用這些函數(shù)。
程序清單L 9.9 18.2Hz 的OSTickISR()函數(shù).
_OSTickISRPROCFAR
;
PUSHA; 保存被中斷任務(wù)的CPU環(huán)境
PUSHES
PUSHDS
;
MOVAX,SEG_OSIntNesting;載入 DS
MOVDS,AX
;
INCBYTEPTR_OSIntNesting;標(biāo)示uC/OS-II進(jìn)入中斷
;
INT081H; 調(diào)用DOS的時(shí)鐘中斷處理函數(shù)
;
CALLFARPTR_OSTimeTick; 調(diào)用OSTimeTick()函數(shù)
;
CALLFARPTR_OSIntExit;標(biāo)示uC/OS-IIof中斷結(jié)束
;
POPDS; 恢復(fù)被中斷任務(wù)的CPU環(huán)境
POPES
POPA
;
IRET; 返回被中斷任務(wù)
;
_OSTickISRENDP
圖F9.7 傳遞參數(shù) pdata的堆棧初始化結(jié)構(gòu)
9.05.01 OSTaskStkInit()
該函數(shù)由OSTaskCreate()或OSTaskCreateExt()調(diào)用,用來(lái)初始化任務(wù)的堆棧。初始狀態(tài)的堆棧模擬發(fā)生一次中斷后的堆棧結(jié)構(gòu)。圖F9.7說(shuō)明了OSTaskStkInit()初始化后的堆棧內(nèi)容。請(qǐng)注意,圖中的堆棧結(jié)構(gòu)不是調(diào)用OSTaskStkInit()任務(wù)的,而是新創(chuàng)建任務(wù)的。
當(dāng)調(diào)用OSTaskCreate()或OSTaskCreateExt()創(chuàng)建一個(gè)新任務(wù)時(shí),需要傳遞的參數(shù)是:
任務(wù)代碼的起使地址,參數(shù)指針(pdata),任務(wù)堆棧頂端的地址,任務(wù)的優(yōu)先級(jí)。
OSTaskCreateExt()還需要一些其他參數(shù),但與OSTask StkInit()沒(méi)有關(guān)系。
OSTaskStkInit()(程序清單L9.10)只需要以上提到的3個(gè)參數(shù)(task,pdata,和ptos)。
程序清單L 9.10 OSTaskStkInit().
void*OSTaskStkInit(void(*task)(void*pd),void*pdata,void*ptos,INT16Uopt)
{
INT16U*stk;
opt=opt;/*‘opt‘未使用,此處可防止編譯器的警告 */
stk=(INT16U*)ptos;/* 載入堆棧指針 (1)*/
*stk--=(INT16U)FP_SEG(pdata);/* 放置向函數(shù)傳遞的參數(shù) (2)*/
*stk--=(INT16U)FP_OFF(pdata);
*stk--=(INT16U)FP_SEG(task);/* 函數(shù)返回地址(3)*/
*stk--=(INT16U)FP_OFF(task);
*stk--=(INT16U)0x0202;/*SW 設(shè)置為中斷開(kāi)啟 (4)*/
*stk--=(INT16U)FP_SEG(task);/* 堆棧頂端放置指向任務(wù)代碼的指針*/
*stk--=(INT16U)FP_OFF(task);
*stk--=(INT16U)0xAAAA;/*AX=0xAAAA(5)*/
*stk--=(INT16U)0xCCCC;/*CX=0xCCCC*/
*stk--=(INT16U)0xDDDD;/*DX=0xDDDD*/
*stk--=(INT16U)0xBBBB;/*BX=0xBBBB*/
*stk--=(INT16U)0x0000;/*SP=0x0000*/
*stk--=(INT16U)0x1111;/*BP=0x1111*/
*stk--=(INT16U)0x2222;/*SI=0x2222*/
*stk--=(INT16U)0x3333;/*DI=0x3333*/
*stk--=(INT16U)0x4444;/*ES=0x4444*/
*stk=_DS;/*DS=當(dāng)前CPU的 DS寄存器 (6)*/
return((void*)stk);
}
由于80x86堆棧是16位寬的(以字為單位)[程序清單L9.10(1)],OSTaskStkInit()將創(chuàng)立一個(gè)指向以字為單位內(nèi)存區(qū)域的指針。同時(shí)要求堆棧指針指向空堆棧的頂端。
筆者使用的BorlandC/C++編譯器配置為用堆棧而不是寄存器來(lái)傳送參數(shù)pdata,此時(shí)參數(shù)pdata的段地址和偏移量都將被保存在堆棧中[程序清單L9.10(2)]。
堆棧中緊接著是任務(wù)函數(shù)的起始地址[程序清單L9.10(3)],理論上,此處應(yīng)該為任務(wù)的返回地址,但在μC/OS-II中,任務(wù)函數(shù)必須為無(wú)限循環(huán)結(jié)構(gòu),不能有返回點(diǎn)。
返回地址下面是狀態(tài)字(SW)[程序清單L9.10(4)], 設(shè)置狀態(tài)字也是為了模擬中斷發(fā)生后的堆棧結(jié)構(gòu)。堆棧中的SW初始化為0x0202,這將使任務(wù)啟動(dòng)后允許中斷發(fā)生;如果設(shè)為0x0002,則任務(wù)啟動(dòng)后將禁止中斷。需要注意的是,如果選擇任務(wù)啟動(dòng)后允許中斷發(fā)生,則所有的任務(wù)運(yùn)行期間中斷都允許;同樣,如果選擇任務(wù)啟動(dòng)后禁止中斷,則所有的任務(wù)都禁止中斷發(fā)生,而不能有所選擇。
如果確實(shí)需要突破上述限制,可以通過(guò)參數(shù)pdata向任務(wù)傳遞希望實(shí)現(xiàn)的中斷狀態(tài)。如果某個(gè)任務(wù)選擇啟動(dòng)后禁止中斷,那么其他的任務(wù)在運(yùn)行的時(shí)候需要重新開(kāi)啟中斷。同時(shí)還要修改OSTaskIdle()和OSTaskStat()函數(shù),在運(yùn)行時(shí)開(kāi)啟中斷。如果以上任何一個(gè)環(huán)節(jié)出現(xiàn)問(wèn)題,系統(tǒng)就會(huì)崩潰。所以筆者還是推薦用戶(hù)設(shè)置SW為0x0202,在任務(wù)啟動(dòng)時(shí)開(kāi)啟中斷。
堆棧中還要留出各個(gè)寄存器的空間,注意寄存器在堆棧中的位置要和運(yùn)行指令PUSHA,PUSHES,和PUSHDS和壓入堆棧的次序相同。 上述指令在每次進(jìn)入中斷服務(wù)程序時(shí)都會(huì)調(diào)用[程序清單L9.10(5)]。AX,BX,CX,DX,SP,BP,SI,和DI的次序是和指令PUSHA的壓棧次序相同的。如果使用沒(méi)有PUSHA指令的8086處理器,就要使用多個(gè)PUSH指令壓入上述寄存器,且順序要與PUSHA相同。 在程序清單L9.10中每個(gè)寄存器被初始化為不同的值, 這是為了調(diào)試方便。
Borland編譯器支持偽寄存器變量操作,可以用_DS關(guān)鍵字取得CPUDS寄存器的值,程序清單
L9.10中(6)標(biāo)記處用_DS直接把DS寄存器拷貝到堆棧中。
堆棧初始化工作結(jié)束后,OSTaskStkInit()返回新的堆棧棧頂指針,OSTaskCreate()或
OSTaskCreateExt()將指針保存在任務(wù)的OS_TCB中。
9.05.02 OSTaskCreateHook()
OS_CPU_C.C中未定義,此函數(shù)為用戶(hù)定義。
9.05.03 OSTaskDelHook()
OS_CPU_C.C中未定義,此函數(shù)為用戶(hù)定義。
9.05.04 OSTaskSwHook()
OS_CPU_C.C中未定義,此函數(shù)為用戶(hù)定義。其用法請(qǐng)參考例程3。
9.05.05 OSTaskStatHook()[!--empirenews.page--]
OS_CPU_C.C中未定義,此函數(shù)為用戶(hù)定義。其用法請(qǐng)參考例程3。
9.05.06 OSTimeTickHook()
OS_CPU_C.C中未定義,此函數(shù)為用戶(hù)定義。
9.06 內(nèi)存占用
表9.1列出了指定初始化常量的情況下,μC/OS-II占用內(nèi)存的情況,包括數(shù)據(jù)和程序代碼。如果μC/OS-II用于嵌入式系統(tǒng),則數(shù)據(jù)指RAM的占用,程序代碼指ROM的占用。內(nèi)存占用的說(shuō)明清單隨磁盤(pán)一起提供給用戶(hù),在安裝μC/OS-II后,查看SOFTWAREuCOS-
IIIx836LDOC目錄下的ROM-RAM.XLS文件。 該文件為MicrosoftExcel文件, 需要Office97
或更高版本的Excel打開(kāi)。
表9.1中所列出的內(nèi)存占用大小都近似為25字節(jié)的倍數(shù)。筆者所用的BorlandC/C++V3.1設(shè)定為編譯產(chǎn)生運(yùn)行速度最快的目標(biāo)代碼,所以表中所列的數(shù)字并不是絕對(duì)的,但可以給讀者一個(gè)總的概念。例如,如果不使用消息隊(duì)列機(jī)制,在編譯前將OS_Q_EN設(shè)為0,則編譯后的目標(biāo)代碼長(zhǎng)度6,875字節(jié),可減小大約1,475字節(jié)。
此外,空閑任務(wù)(idle)和統(tǒng)計(jì)任務(wù)(statistics)的堆棧都設(shè)為1,024字節(jié)(1Kb)。根據(jù)您自己的要求可以增減。μC/OS-II的數(shù)據(jù)結(jié)構(gòu)最少需要35字節(jié)的RAM。
表9.2說(shuō)明了如何裁減μC/OS-II,應(yīng)用在更小規(guī)模的系統(tǒng)上。此處的小系統(tǒng)有16個(gè)任務(wù)。
并且不采用如下功能:
?郵箱功能(OS_MBOX_EN設(shè)為0)
?內(nèi)存管理機(jī)制(OS_MEM_EN設(shè)為0)
?動(dòng)態(tài)改變?nèi)蝿?wù)優(yōu)先級(jí)(OS_TASK_CHANGE_PRIO_EN設(shè)為0)
?舊版本的任務(wù)創(chuàng)建函數(shù)OSTaskCreate()(OS_TASK_CREATE_EN設(shè)為0)
?任務(wù)刪除(OS_TASK_DEL_EN設(shè)為0)
?掛起和喚醒任務(wù)(OS_TASK_SUSPEND_EN設(shè)為0)
采取上述措施后, 程序代碼空間可以減小3Kb, 數(shù)據(jù)空間可以減小2,200字節(jié)。 由于只有16個(gè)任務(wù)運(yùn)行,節(jié)省了大量用于任務(wù)控制塊OS_TCB的空間。在80x86的大模式編譯條件下,每一個(gè)OS_TCB將占用45字節(jié)的RAM。
9.07 運(yùn)行時(shí)間
表9.3到9.5列出了大部分μC/OS-II函數(shù)在80186處理器上的運(yùn)行時(shí)間。 統(tǒng)計(jì)的方法是將C原程序編譯為匯編代碼,然后計(jì)算每條匯編指令所需的時(shí)鐘周期,根據(jù)處理器的時(shí)鐘頻率,最后算出運(yùn)行時(shí)間。表中的I 欄為函數(shù)包含有多少條指令,C 欄為函數(shù)運(yùn)行需要多少時(shí)鐘周期,μs為運(yùn)行所需的以微秒為單位的時(shí)間。表中有3類(lèi)時(shí)間,分別是在函數(shù)中關(guān)閉中斷的時(shí)間、函數(shù)運(yùn)行的最小時(shí)間和最大時(shí)間。如果您不使用80186處理器,表中的數(shù)據(jù)就沒(méi)有什么實(shí)際意義,但可以使您理解每個(gè)函數(shù)運(yùn)行時(shí)間的相對(duì)大小。
表 9.1μC/OS-II 內(nèi)存占用( 80186).
表 9.2 壓縮后的μC/OS-II配置.
以上各表中的時(shí)間數(shù)據(jù)都是假設(shè)函數(shù)成功運(yùn)行,正常返回;同時(shí)假定處理器工作在最大總線速度。平均來(lái)說(shuō),80186處理器的每條指令需要10個(gè)時(shí)鐘周期。
對(duì)于80186處理器,μC/OS-II中的函數(shù)最大的關(guān)閉中斷時(shí)間是33.3μs,約1,100個(gè)時(shí)鐘周期。
N/A是指該函數(shù)的運(yùn)行時(shí)間長(zhǎng)短并不重要,例如一些只執(zhí)行一次初始化函數(shù)。
如果您用的是x86系列的其他CPU,您可以根據(jù)表中每個(gè)函數(shù)的運(yùn)行時(shí)鐘周期項(xiàng)估計(jì)當(dāng)前CPU的執(zhí)行時(shí)間。例如,如果用80486,且知80486的指令平均用2個(gè)時(shí)鐘周期;或者知道80486總線頻率為66MHz(比80186的33MHz快2倍),都可以估計(jì)出函數(shù)在80486上的執(zhí)行時(shí)間。
表 9.3μC/OS-II函數(shù)在33MHz80186上的執(zhí)行時(shí)間.
表9.3μC/OS-II函數(shù)在33MHz80186上的執(zhí)行時(shí)間.(續(xù)表)內(nèi)存管理
表9.3μC/OS-II函數(shù)在33MHz80186上的執(zhí)行時(shí)間.(續(xù)表)
下面我們將討論每個(gè)函數(shù)的關(guān)閉中斷時(shí)間,最大、最小運(yùn)行時(shí)間是如何計(jì)算的,以及這樣計(jì)算的先決條件。
OSSchedUnlock()
最小運(yùn)行時(shí)間是當(dāng)變量OSLockNesting減為0,且系統(tǒng)中沒(méi)有更高優(yōu)先級(jí)的任務(wù)就緒,SSchedUnlock()正常結(jié)束返回調(diào)用者。
最大運(yùn)行時(shí)間是也是當(dāng)變量OSLockNesting減為0,但有更高優(yōu)先級(jí)的任務(wù)就緒,函數(shù)中需要進(jìn)行任務(wù)切換。
OSIntExit()
最小運(yùn)行時(shí)間是當(dāng)變量OSLockNesting減為0,且系統(tǒng)中沒(méi)有更高優(yōu)先級(jí)的任務(wù)就緒,OSIntExit()正常結(jié)束返回被中斷任務(wù)。
最大運(yùn)行時(shí)間是也是當(dāng)變量OSLockNesting減為0,但有更高優(yōu)先級(jí)的任務(wù)就緒,OSIntExit()將不返回調(diào)用者,經(jīng)過(guò)任務(wù)切換操作后,將直接返回就緒的任務(wù)。
OSTickISR()
此函數(shù)假定在當(dāng)前μC/OS-II中運(yùn)行有最大數(shù)目的任務(wù)(64個(gè))。
最小運(yùn)行時(shí)間是當(dāng)64個(gè)任務(wù)都不在等待延時(shí)狀態(tài)。也就是說(shuō),所有的任務(wù)都不需要OSTickISR()處理。
最大運(yùn)行時(shí)間是當(dāng)63個(gè)任務(wù)(空閑進(jìn)程不會(huì)延時(shí)等待)都處于延時(shí)狀態(tài),此時(shí)OSTickISR()需要逐個(gè)檢查等待中的任務(wù),將計(jì)數(shù)器減1,并判斷是否延時(shí)結(jié)束。這種情況對(duì)于系統(tǒng)是一個(gè)很重的負(fù)荷。例如在最壞的情況,設(shè)時(shí)鐘節(jié)拍間隔10ms,OSTickISR()需要625μs,占了約6%的CPU利用率。但請(qǐng)注意,此時(shí)所有的任務(wù)都沒(méi)有執(zhí)行,只是內(nèi)核的開(kāi)銷(xiāo)。
OSMboxPend()
最小運(yùn)行時(shí)間是當(dāng)郵箱中有消息需要處理的時(shí)候。
最大運(yùn)行時(shí)間是當(dāng)郵箱中沒(méi)有消息,任務(wù)需要等待的時(shí)候。此時(shí)調(diào)用OSMboxPend()的任務(wù)將被掛起,進(jìn)行任務(wù)切換。最大運(yùn)行時(shí)間是同一任務(wù)執(zhí)行OSMboxPend()的累計(jì)時(shí)間,這個(gè)過(guò)程包括OSMboxPend()查看郵箱,發(fā)現(xiàn)沒(méi)有消息,再調(diào)用任務(wù)切換函數(shù)OSSched(),切換到新任務(wù)。當(dāng)由于某種原因調(diào)用OSMboxPend()的任務(wù)又被喚醒執(zhí)行,從OSSched()中返回,發(fā)現(xiàn)返回的原因是由于延時(shí)結(jié)束(處理延時(shí)結(jié)束情況的代碼最長(zhǎng)—譯者注),最后返回調(diào)用任務(wù)。OSMboxPend()的最大運(yùn)行時(shí)間是上述時(shí)間的總和。[!--empirenews.page--]
OSMboxPost()
最小運(yùn)行時(shí)間是當(dāng)郵箱是空的,沒(méi)有任務(wù)等待消息的時(shí)候。
最大運(yùn)行時(shí)間是當(dāng)消息郵箱中有一個(gè)或多個(gè)任務(wù)在等待消息。此時(shí),消息將發(fā)往等待隊(duì)列中優(yōu)先級(jí)最高的任務(wù),將此任務(wù)喚醒執(zhí)行。最大運(yùn)行時(shí)間是同一任務(wù)執(zhí)行OSMboxPost()的累計(jì)時(shí)間,這個(gè)過(guò)程包括任務(wù)喚醒等待任務(wù),發(fā)送消息,調(diào)用任務(wù)切換函數(shù)OSSched(),切換到新任務(wù)。當(dāng)由于某種原因調(diào)用OSMboxPost()的任務(wù)又被喚醒執(zhí)行,從OSSched()中返回,發(fā)現(xiàn)返回的原因是由于延時(shí)結(jié)束(處理延時(shí)結(jié)束情況的代碼最長(zhǎng)—譯者注),最后返回調(diào)用任務(wù)。OSMboxPost()的最大運(yùn)行時(shí)間是上述時(shí)間的總和。
OSMemGet()
最小運(yùn)行時(shí)間是當(dāng)系統(tǒng)中已經(jīng)沒(méi)有內(nèi)存塊,OSMemGet()返回錯(cuò)誤碼。
最大運(yùn)行時(shí)間是OSMemGet()獲得了內(nèi)存塊,返回調(diào)用者。
OSMemPut()
最小運(yùn)行時(shí)間是當(dāng)向一個(gè)已經(jīng)排滿的內(nèi)存分區(qū)中返回內(nèi)存塊。
最大運(yùn)行時(shí)間是當(dāng)向一個(gè)未排滿的內(nèi)存分區(qū)中返回內(nèi)存塊
OSQPend()
最小運(yùn)行時(shí)間是當(dāng)消息隊(duì)列中有消息需要處理的時(shí)候。
最大運(yùn)行時(shí)間是當(dāng)消息隊(duì)列中沒(méi)有消息,任務(wù)需要等待的時(shí)候。此時(shí)調(diào)用OSQPend()的任務(wù)將被掛起,進(jìn)行任務(wù)切換。最大運(yùn)行時(shí)間是同一任務(wù)執(zhí)行OSQPend()的累計(jì)時(shí)間,這個(gè)過(guò)程包括OSQPend()查看消息隊(duì)列,發(fā)現(xiàn)沒(méi)有消息,再調(diào)用任務(wù)切換函數(shù)OSSched(),切換到新任務(wù)。當(dāng)由于某種原因調(diào)用OSQPend()的任務(wù)又被喚醒執(zhí)行,從OSSched()中返回,發(fā)現(xiàn)返回的原因是由于延時(shí)結(jié)束(處理延時(shí)結(jié)束情況的代碼最長(zhǎng)—譯者注),最后返回調(diào)用任務(wù)。OSQ`Pend()的最大運(yùn)行時(shí)間是上述時(shí)間的總和。
OSQPost()
最小運(yùn)行時(shí)間是當(dāng)消息隊(duì)列是空的,沒(méi)有任務(wù)等待消息的時(shí)候。
最大運(yùn)行時(shí)間是當(dāng)消息隊(duì)列中有一個(gè)或多個(gè)任務(wù)在等待消息。此時(shí),消息將發(fā)往等待隊(duì)列中優(yōu)先級(jí)最高的任務(wù),將此任務(wù)喚醒執(zhí)行。最大運(yùn)行時(shí)間是同一任務(wù)執(zhí)行OSQPost()的累計(jì)時(shí)間,這個(gè)過(guò)程包括任務(wù)喚醒等待任務(wù),發(fā)送消息,調(diào)用任務(wù)切換函數(shù)OSSched(),切換到新任務(wù)。當(dāng)由于某種原因調(diào)用OSQPost()的任務(wù)又被喚醒執(zhí)行,從OSSched()中返回,發(fā)現(xiàn)返回的原因是由于延時(shí)結(jié)束(處理延時(shí)結(jié)束情況的代碼最長(zhǎng)—譯者注),最后返回調(diào)用任務(wù)。OSQPost()的最大運(yùn)行時(shí)間是上述時(shí)間的總和。
OSQPostFront()
此函數(shù)與OSQPost()的過(guò)程相同。
OSSemPend()
最小運(yùn)行時(shí)間是當(dāng)信號(hào)量可獲取的時(shí)候(信號(hào)量計(jì)數(shù)器大于0)。
最大運(yùn)行時(shí)間是當(dāng)信號(hào)量不可得,任務(wù)需要等待的時(shí)候。此時(shí)調(diào)用OSSemPend()的任務(wù)將被掛起,進(jìn)行任務(wù)切換。最大運(yùn)行時(shí)間是同一任務(wù)執(zhí)行OSQPend()的累計(jì)時(shí)間,這個(gè)過(guò)程包括OSSemPend()查看信號(hào)量計(jì)數(shù)器,發(fā)現(xiàn)是0,再調(diào)用任務(wù)切換函數(shù)OSSched(),切換到新任務(wù)。當(dāng)由于某種原因調(diào)用OSSemPend()的任務(wù)又被喚醒執(zhí)行,從OSSched()中返回,發(fā)現(xiàn)返回的原因是由于延時(shí)結(jié)束(處理延時(shí)結(jié)束情況的代碼最長(zhǎng)—譯者注),最后返回調(diào)用任務(wù)。OSSemPend()的最大運(yùn)行時(shí)間是上述時(shí)間的總和。
OSSemPost()
最小運(yùn)行時(shí)間是當(dāng)沒(méi)有任務(wù)在等待信號(hào)量的時(shí)候。
最大運(yùn)行時(shí)間是當(dāng)有一個(gè)或多個(gè)任務(wù)在等待信號(hào)量。此時(shí),等待隊(duì)列中優(yōu)先級(jí)最高的任務(wù)將被喚醒執(zhí)行。最大運(yùn)行時(shí)間是同一任務(wù)執(zhí)行OSSemPost()的累計(jì)時(shí)間,這個(gè)過(guò)程包括任務(wù)喚醒等待任務(wù),調(diào)用任務(wù)切換函數(shù)OSSched(),切換到新任務(wù)。當(dāng)由于某種原因調(diào)用OSSemPost()的任務(wù)又被喚醒執(zhí)行,從OSSched()中返回,發(fā)現(xiàn)返回的原因是由于延時(shí)結(jié)束(處理延時(shí)結(jié)束情況的代碼最長(zhǎng)—譯者注),最后返回調(diào)用任務(wù)。OSSemPost()的最大運(yùn)行時(shí)間是上述時(shí)間的總和。
OSTaskChangePrio()
最小運(yùn)行時(shí)間是當(dāng)任務(wù)被改變的優(yōu)先級(jí)比當(dāng)前運(yùn)行任務(wù)的低,此時(shí)不進(jìn)行任務(wù)切換,直接返回調(diào)用任務(wù)。
最大運(yùn)行時(shí)間是當(dāng)任務(wù)被改變的優(yōu)先級(jí)比當(dāng)前運(yùn)行任務(wù)的高,此時(shí)將進(jìn)行任務(wù)切換。
OSTaskCreate()
最小運(yùn)行時(shí)間是當(dāng)調(diào)用OSTaskCreate()的任務(wù)創(chuàng)建了一個(gè)比自己優(yōu)先級(jí)低的任務(wù), 此時(shí)不進(jìn)行任務(wù)切換。
最大運(yùn)行時(shí)間是當(dāng)調(diào)用OSTaskCreate()的任務(wù)創(chuàng)建了一個(gè)比自己優(yōu)先級(jí)高的任務(wù), 此時(shí)將進(jìn)行任務(wù)切換。
上述兩種情況都是假定OSTaskCreateHook()不進(jìn)行任何操作。
OSTaskCreateExt()
最小運(yùn)行時(shí)間是當(dāng)OSTaskCreateExt()不對(duì)堆棧進(jìn)行清零操作(此項(xiàng)操作是為堆棧檢查函數(shù)做準(zhǔn)備的)。
最大運(yùn)行時(shí)間是當(dāng)OSTaskCreateExt()需要進(jìn)行堆棧清零操作。但此項(xiàng)操作的時(shí)間取決于堆棧的大小。如果設(shè)清除每個(gè)堆棧單元(堆棧操作以字為單位—譯者注)需要100個(gè)時(shí)鐘周期(3μs),1000字節(jié)的堆棧將需要1,500μs(1000字節(jié)除以2再乘以3μs/每字)。在清除堆棧過(guò)程中中斷是打開(kāi)的,可以響應(yīng)中斷請(qǐng)求。
上述兩種情況都是假定OSTaskCreateHook()不進(jìn)行任何操作。
OSTaskDel()
最小運(yùn)行時(shí)間是當(dāng)被刪除的任務(wù)不是當(dāng)前任務(wù),此時(shí)不進(jìn)行任務(wù)切換。
最大運(yùn)行時(shí)間是當(dāng)被刪除的任務(wù)是當(dāng)前任務(wù),此時(shí)將進(jìn)行任務(wù)切換。
OSTaskDelReq()
該函數(shù)很短,幾乎沒(méi)有最小和最大運(yùn)行時(shí)間之分。
OSTaskResume()
最小運(yùn)行時(shí)間是當(dāng)OSTaskResume()喚醒了一個(gè)任務(wù),但該任務(wù)的優(yōu)先級(jí)比當(dāng)前任務(wù)低,此時(shí)不進(jìn)行任務(wù)切換。
最大運(yùn)行時(shí)間是OSTaskResume()喚醒了一個(gè)優(yōu)先級(jí)更高的任務(wù),此時(shí)將進(jìn)行任務(wù)切換。
OSTaskStkChk()
OSTaskStkChk()的執(zhí)行過(guò)程是從堆棧的底端開(kāi)始檢查0的個(gè)數(shù),估計(jì)堆棧所剩的空間。
所以最小運(yùn)行時(shí)間是當(dāng)OSTaskStkChk()檢查一個(gè)全部占滿的堆棧。 但實(shí)際上這種情況是不允許發(fā)生的,這將使系統(tǒng)崩潰。
最大運(yùn)行時(shí)間是當(dāng)OSTaskStkChk()檢查一個(gè)全空堆棧,執(zhí)行時(shí)間取決于堆棧的大小。例如檢查每個(gè)堆棧單元(堆棧操作以字為單位—譯者注)需要80鐘周期(2.4μs),1000字節(jié)的堆棧將需要1,200μs(1000字節(jié)除以2再乘以2.4μs/每字)。再加上其他的一些操作,總共需要大約1,218μs。在檢查堆棧過(guò)程中中斷是打開(kāi)的,可以響中斷請(qǐng)求。
OSTaskSuspend()
最小運(yùn)行時(shí)間是當(dāng)被掛起的任務(wù)不是當(dāng)前任務(wù),此時(shí)不進(jìn)行任務(wù)切換。[!--empirenews.page--]
最大運(yùn)行時(shí)間是當(dāng)前任務(wù)掛起自己,此時(shí)將進(jìn)行任務(wù)切換。
OSTaskQuery()
該函數(shù)的運(yùn)行時(shí)間總是一樣的。OSTaskQuery()執(zhí)行的操作是獲取任務(wù)的任務(wù)控制塊OS_TCB。如果OS_TCB中包含所有的操作項(xiàng),需要占用45字節(jié)(大模式編譯)。
OSTimeDly()
如果延時(shí)時(shí)間不為0,則OSTimeDly()運(yùn)行時(shí)間總是相同的。此時(shí)將進(jìn)行任務(wù)切換。
如果延時(shí)時(shí)間為0,OSTimeDly()不清除OSRdyGrp中的任務(wù)就緒位,不進(jìn)行延時(shí)操作,直接返回。
OSTimeDlyHMSM()
如果延時(shí)時(shí)間不為0,則OSTimeDlyHMSM()運(yùn)行時(shí)間總是相同的。此時(shí)將進(jìn)行任務(wù)切換。
此外,OSTimeDlyHMSM()延時(shí)時(shí)間最好不要超過(guò)65,536個(gè)時(shí)鐘節(jié)拍。也就是說(shuō),如果時(shí)鐘節(jié)拍發(fā)生的間隔為10ms(頻率100Hz),延時(shí)時(shí)間應(yīng)該限定在10分55秒350毫秒內(nèi)。如果超過(guò)了上述數(shù)值,該任務(wù)就不能用OSTimeDlyResume()函數(shù)喚醒。
OSTimeDlyResume()
最小運(yùn)行時(shí)間是當(dāng)被喚醒的任務(wù)優(yōu)先級(jí)低于當(dāng)前任務(wù),此時(shí)不進(jìn)行任務(wù)切換。
最大運(yùn)行時(shí)間是當(dāng)喚醒了一個(gè)優(yōu)先級(jí)更高的任務(wù),此時(shí)將進(jìn)行任務(wù)切換。
OSTimeTick()
前面我們討論的OSTickISR()函數(shù)其實(shí)就是OSTimeTick()與OSIntEnter()、 OSIntExit()
的組合。OSTickISR()的時(shí)間占用情況就是OSTimeTick()的占用情況。以下討論假定系統(tǒng)中有μC/OS-II允許的最大數(shù)量的任務(wù)(64個(gè))。
最小運(yùn)行時(shí)間是當(dāng)64個(gè)任務(wù)都不在等待延時(shí)狀態(tài)。也就是說(shuō),所有的任務(wù)都不需要OSTimeTick()處理。
最大運(yùn)行時(shí)間是當(dāng)63個(gè)任務(wù) (空閑進(jìn)程不會(huì)延時(shí)等待) 都處于延時(shí)狀態(tài), 此時(shí)OSTimeTick()
需要逐個(gè)檢查等待中的任務(wù),將計(jì)數(shù)器減1,并判斷是否延時(shí)結(jié)束。例如在最壞的情況,設(shè)時(shí)鐘節(jié)拍間隔10ms,OSTimeTick()需要約600μs,占了6%的CPU利用率
表 9.4 各函數(shù)的執(zhí)行時(shí)間(按關(guān)閉中斷時(shí)間排序).
表9.5 各函數(shù)的執(zhí)行時(shí)間(按最大運(yùn)行時(shí)間排序).