基于μC/OS-II的時(shí)間片調度法的設計與應用
引言
μC/OS-II嵌入式實(shí)時(shí)操作系統采用的是基于優(yōu)先級的可剝奪調度法[1]?;趦?yōu)先級的可剝奪調度法是指,CPU總是讓處于就緒態(tài)的、優(yōu)先級最高的任務(wù)運行;最高優(yōu)先級的任務(wù)一旦就緒,總能得到CPU的使用權,當一個(gè)運行著(zhù)的任務(wù)使一個(gè)比它優(yōu)先級高的任務(wù)進(jìn)入了就緒態(tài)時(shí),當前任務(wù)的CPU使用權就被剝奪了,更高優(yōu)先級的任務(wù)立刻得到了CPU的使用權。除非最高優(yōu)先級的任務(wù)主動(dòng)放棄CPU的使用權(通過(guò)調用OSTimeDly()、OSSemPend()等函數),否則低優(yōu)先級的任務(wù)是沒(méi)機會(huì )獲得CPU使用權的。對于一個(gè)實(shí)際應用系統中耗時(shí)比較長(cháng)的任務(wù),為了讓其他任務(wù)能夠得到實(shí)時(shí)調度,可以用兩種方法來(lái)處理。第一種方法是把該任務(wù)的優(yōu)先級設為最低(當然還是比空閑任務(wù)要高);第二種方法就是讓該耗時(shí)任務(wù)運行一段時(shí)間后延時(shí)一下再繼續運行,即把整個(gè)任務(wù)劃分為若干步驟來(lái)執行,如以下的示例代碼:
很多情況下,耗時(shí)長(cháng)的任務(wù)并不能設置為最低優(yōu)先級任務(wù),而劃分步驟來(lái)執行的方法不但繁瑣而且每一步執行的時(shí)間也是不確定的(其他低優(yōu)先級任務(wù)獲得CPU使用權的時(shí)間也會(huì )是不確定的)。筆者在用μC/OSII開(kāi)發(fā)一款車(chē)載信息娛樂(lè )系統的時(shí)候就碰到了這樣的問(wèn)題,因此設計了一種優(yōu)先級和時(shí)間片相結合的調度法(也就是基于μC/OSII的時(shí)間片調度法)。
1 調度原理
這種調度法給處于就緒態(tài)的每一個(gè)任務(wù)都分配一個(gè)時(shí)間片(優(yōu)先級越高分配的時(shí)間片越長(cháng),空閑任務(wù)得不到時(shí)間片的分配),內核按照任務(wù)的優(yōu)先級依次調度處于就緒態(tài)的任務(wù),即當就緒態(tài)中最高優(yōu)先級的任務(wù)用完自己的時(shí)間片后,CPU控制權轉讓給就緒態(tài)中優(yōu)先級第二高的任務(wù)。該任務(wù)用完自己的時(shí)間片后,CPU控制權又轉讓給下一優(yōu)先級的就緒態(tài)任務(wù)……當就緒態(tài)的每一個(gè)任務(wù)都被調度一次之后將重新為它們分配時(shí)間片,然后又開(kāi)始新一輪的調度……[2]
其中要注意的是,在調度過(guò)程中如果有一個(gè)比當前任務(wù)優(yōu)先級更高的任務(wù)由其他態(tài)變成了就緒態(tài)(被創(chuàng )建或獲取了一個(gè)信號量等),當前任務(wù)的CPU控制權將被剝奪;空閑任務(wù)仍然是等到其他任務(wù)都退出就緒態(tài)才獲得CPU的使用權。
圖1解釋了該調度法的調度過(guò)程(其中任務(wù)1優(yōu)先級最高,任務(wù)2次之,任務(wù)3最低)。
圖1 基于μC/OSII時(shí)間片調度過(guò)程
?、?任務(wù)2和任務(wù)3都處于就緒態(tài),任務(wù)1在等待一個(gè)信號量,優(yōu)先級中的任務(wù)2獲得CPU使用權。
?、?任務(wù)2的時(shí)間片用完,優(yōu)先級低的任務(wù)3獲得CPU使用權。
?、?任務(wù)3的時(shí)間片用完,任務(wù)2重新獲得CPU的使用權。
?、?任務(wù)2的時(shí)間片還沒(méi)用完時(shí)中斷來(lái)臨,中斷服務(wù)程序獲得CPU使用權。
?、?中斷服務(wù)程序發(fā)送了一個(gè)任務(wù)1等待的信號量,中斷服務(wù)完成后優(yōu)先級高的任務(wù)1獲得CPU使用權。
?、?任務(wù)1的時(shí)間片用完,任務(wù)2繼續運行。
?、?任務(wù)2的時(shí)間片用完,任務(wù)3獲得CPU使用權。
?、?任務(wù)3的時(shí)間片用完,重新分配時(shí)間片,新一輪調度開(kāi)始。
2 實(shí)現方法
在調度算法的實(shí)現過(guò)程中,力求做到3點(diǎn):
?、?盡可能少地改動(dòng)μC/OSII原有的代碼;
?、?增加的代碼在風(fēng)格上保持與原有的相一致;
?、?兼容原有的優(yōu)先級調度法(可以很方便地選擇優(yōu)先級調度法或是時(shí)間片調度法)。
注:對于該小節中出現的代碼,如果是筆者增加的部分都用黑體表示。
2.1 數據結構中增加的變量
在進(jìn)程控制塊中增加兩項:
Typedef struct os_tcb{
……
#if OS_TASK_TIME_SLICE_EN>0
/*條件編譯,OS_TASK_TIME_SLICE_EN在os_cfg.h中定義,凡是涉及與時(shí)間片調度相關(guān)的代碼都用條件編譯。這樣,可以通過(guò)更改配置文件很方便地選擇任務(wù)調度法
*/INT16UOSTCBTimeSlice;
/*任務(wù)的時(shí)間片大小,在任務(wù)創(chuàng )建時(shí)被初始化,運行過(guò)程中保持不變*/
INT16UOSTCBCounter;
/*任務(wù)運行剩余時(shí)間計數器,每一輪調度開(kāi)始時(shí)該變量被賦值(等于OSTCBTimeSlice),運行過(guò)程中不斷遞減。當其等于0時(shí)任務(wù)被剝奪CPU使用權*/
#endif
}
由于當前任務(wù)的時(shí)間片使用完時(shí),該任務(wù)將被從就緒表OSRdyGrp以及OSRdyTbl[OS_RDY_TBL_SIZE]中清除;新一輪調度開(kāi)始時(shí)它又必須被恢復,因此筆者在uCOS_II.h文件中增加以下變量(不妨把它們稱(chēng)為“時(shí)間片調度表”)分別用于保存OSRdyGrp和OSRdyTbl[OS_RDY_TBL_SIZE]。
OS_EXT INT8UOSTSSGrp;
OS_EXT INT8UOSTSSTbl[OS_RDY_TBL_SIZE];
另外,在uCOS_II.h文件中增加宏定義,用于表示任務(wù)時(shí)間片被用完這種狀態(tài):
#defineOS_STAT_TS_USEUP0x40
2.2 相關(guān)函數的修改
對OS_TCBInit()、OSTimeTick()、OSTimeDly()、OS_EventTaskWait()、OS_EventTaskRdy()這5個(gè)函數的修改,是在μC/OSII基礎上實(shí)現時(shí)間片調度法的關(guān)鍵。下面將一一對這幾個(gè)函數的修改部分進(jìn)行說(shuō)明。
在初始化任務(wù)控制塊的函數OS_TCBInit()中,筆者添加以下代碼讓新創(chuàng )建的任務(wù)處于時(shí)間片就緒表中,并根據任務(wù)優(yōu)先級對任務(wù)的時(shí)間片大小進(jìn)行初始化。
OSTimeTick()函數在每個(gè)時(shí)鐘滴答被調用,在時(shí)間片調度過(guò)程中起到了遞減時(shí)間片計數器的作用。當計數器為0時(shí),進(jìn)行任務(wù)切換或是重新給各個(gè)任務(wù)分配時(shí)間片并開(kāi)始新一輪調度。
OSTimeDly()函數的作用是將任務(wù)延時(shí)一定的時(shí)間。這種情況下,應該把該任務(wù)從時(shí)間片調度表中清除。
當某個(gè)任務(wù)須等待一個(gè)事件的發(fā)生時(shí),信號量、互斥型信號量、郵箱及消息隊列會(huì )通過(guò)相應的PEND函數調用函數OS_EventTaskWait(),使當前任務(wù)從就緒任務(wù)表中脫離就緒態(tài),此時(shí)還需把當前任務(wù)從時(shí)間片調度表中清除。筆者在OS_EventTaskWait()函數中添加了以下代碼:
相應地,當某個(gè)事件發(fā)生了,信號量、互斥型信號量、郵箱及消息隊列會(huì )通過(guò)相應的POST函數調用OS_EventTaskRdy(),從等待任務(wù)隊列中使最高優(yōu)先級任務(wù)脫離等待狀態(tài),此時(shí)還需要把該任務(wù)添加到時(shí)間片調度表中。筆者在OS_EventTaskRdy()函數中添加了以下代碼:
OSTSSGrp |= bity;
OSTSSTbl[y] |= bitx;
3 應用實(shí)例
筆者首先把μC/OSII移植到開(kāi)發(fā)板上(MCU是意法半導體生產(chǎn)的基于A(yíng)RM7TDMI核的STR730[3]),然后如2小節所述對相關(guān)部分的源代碼進(jìn)行修改,接下來(lái)將優(yōu)先級調度法和基于μC/OSII的時(shí)間片調度法進(jìn)行比較。為此分別建立了2個(gè)任務(wù)Task_TimeConsuming()、Task_Audio(),任務(wù)的優(yōu)先級分別是5、6。
由于模擬的耗時(shí)任務(wù)Task_TimeConsuming()是個(gè)死循環(huán)且沒(méi)有調用OSTimeDly()函數,其優(yōu)先級又比Task_Audio()高,如果完全按照優(yōu)先級調度,系統不會(huì )有聲音輸出,因為負責聲音控制的任務(wù)Task_Audio一直得不到運行。而如果按照時(shí)間片調度(在os_cfg.h中增加#define OS_TASK_TIME_SLICE_EN 1),則聲音輸出正常,通過(guò)仿真器在Task_Audio()中設置斷點(diǎn),程序會(huì )很快停止在斷點(diǎn)處。進(jìn)一步地,依次在Task_TimeConsuming()和Task_Audio()函數體中設置斷點(diǎn),分別記錄兩次PC指針停止在斷點(diǎn)處時(shí)看門(mén)狗計數器的值WDG_Counter1和WDG_Counter2,可以利用WDG_Counter1和WDG_Counter2的差值估算出任務(wù)Task_Audio前后兩次被調度的時(shí)間間隔(忽略任務(wù)在切換過(guò)程中的耗時(shí))。經(jīng)過(guò)多次計算,這個(gè)時(shí)間間隔值的范圍在58~59ms,而任務(wù)Task_TimeConsuming的時(shí)間片理論值=64-Prio=64-5=59 ms,實(shí)驗值與理論值是非常吻合的。
當然,這只是簡(jiǎn)單的驗證實(shí)驗。嚴格的測試還需要兼顧信號量、互斥型信號量、郵箱及消息隊列相應的PEND、POST函數以及OSTimeDly()函數調用。鑒于篇幅關(guān)系,這里就不再贅述了。
結語(yǔ)
筆者已經(jīng)成功地把這種基于μC/OSII的時(shí)間片調度法運用到車(chē)載信息娛樂(lè )系統的開(kāi)發(fā)中。實(shí)踐證明,對于含有耗時(shí)任務(wù)的系統,尤其是在需要嚴格控制耗時(shí)任務(wù)運行時(shí)間長(cháng)度的場(chǎng)合,該調度算法會(huì )有一定的便捷性,也能保證系統的實(shí)時(shí)響應,而且整個(gè)算法只改動(dòng)了μC/OSII中的少量代碼;還可以根據實(shí)際需要調整各個(gè)任務(wù)的時(shí)間片大小,體現出了算法的實(shí)用性與靈活性。
評論