陳 康, 黃彩虹, 何明華
(1.福州大學(xué) 自動(dòng)化與電氣工程學(xué)院,福建 福州 350007;2.華僑大學(xué) 信息科學(xué)與工程學(xué)院,福建 廈門(mén) 361021)
嵌入式實(shí)時(shí)操作系統(tǒng)(real-time operating system,簡(jiǎn)寫(xiě)為RTOS)具有比較好的擴(kuò)展性,且通過(guò)任務(wù)這個(gè)概念把原有的應(yīng)用程序分割成若干部分。由于采用可剝奪型的內(nèi)核,與前后臺(tái)系統(tǒng)相比,實(shí)時(shí)性得到了更好的保證,所以 RTOS在嵌入式系統(tǒng)中得到了廣泛的應(yīng)用[1]。但現(xiàn)有的幾個(gè)RTOS(Linux,VxWorks,uC/OS-II)代碼量比較大,占用較多的內(nèi)存資源,對(duì)CPU硬件要求較高,目前在工業(yè)控制領(lǐng)域中廣泛應(yīng)用的CPU如8051系列由于其內(nèi)存較小(片內(nèi)存儲(chǔ)器僅128個(gè)字節(jié)),速度較慢(外接晶振為12 M),無(wú)法使用這些操作系統(tǒng)。為提高工控軟件的開(kāi)發(fā)效率,考慮用C語(yǔ)言為其設(shè)計(jì)一種RTOS。
由于目前RTOS常用算法有搶先式與非搶先式2種??紤]到自動(dòng)控制領(lǐng)域?qū)?shí)時(shí)性要求較高,使用搶先式算法,它的主要特點(diǎn)是處于就緒態(tài)的最高優(yōu)先級(jí)任務(wù)始終運(yùn)行并占用著CPU,優(yōu)點(diǎn)在于能夠迅速對(duì)外部事件做出響應(yīng)。在這個(gè)內(nèi)核里,可以把所有的任務(wù)分為掛起、就緒、運(yùn)行、休眠、被中斷態(tài)5種狀態(tài),這些狀態(tài)可以進(jìn)行相互間的轉(zhuǎn)換,如圖1所示。某一時(shí)刻任何任務(wù)都處于5種狀態(tài)之一,操作系統(tǒng)就是要根據(jù)設(shè)計(jì)者的需要實(shí)現(xiàn)這些狀態(tài)之間的轉(zhuǎn)化,并查找就緒任務(wù)中優(yōu)先級(jí)最高的使其迅速進(jìn)入運(yùn)行狀態(tài)。
圖1 任務(wù)狀態(tài)機(jī)
由于單片機(jī)內(nèi)存容量較小,只有128字節(jié),為盡可能地節(jié)省內(nèi)存,在這個(gè)操作系統(tǒng)中使用函數(shù)數(shù)組定義作為基本的數(shù)據(jù)結(jié)構(gòu)[2],其代碼為:
Void(*processTable[TOTALTASK])()。
這個(gè)數(shù)據(jù)結(jié)構(gòu)里定義了各個(gè)任務(wù)的函數(shù)入口地址,主要是為了減少內(nèi)存占用,精簡(jiǎn)代碼量,還可通過(guò)各個(gè)任務(wù)在數(shù)組中的索引來(lái)確定它的優(yōu)先級(jí),索引值越大,優(yōu)先級(jí)越高[3]。另外,為區(qū)分任務(wù)的不同狀態(tài),建立了2個(gè)狀態(tài)表。
(1)休眠狀態(tài)表。用若干個(gè)字節(jié)來(lái)表示(取決于任務(wù)數(shù)),每位表示一個(gè)任務(wù),0表示休眠狀態(tài),1表示非休眠狀態(tài)(包括就緒和掛起態(tài))。
(2)掛起任務(wù)狀態(tài)表。用若干個(gè)字節(jié)來(lái)表示,每位表示一個(gè)任務(wù),休眠狀態(tài)表的該位為1的情況下,掛起任務(wù)狀態(tài)表中的1表示就緒狀態(tài),0表示掛起狀態(tài)??赏ㄟ^(guò)狀態(tài)表中任務(wù)所在字節(jié)的位置來(lái)確定其優(yōu)先級(jí),位數(shù)越高的,優(yōu)先級(jí)越高。
實(shí)時(shí)操作系統(tǒng)運(yùn)行時(shí)需要找到優(yōu)先級(jí)最高的就緒態(tài)任務(wù)并讓其運(yùn)行,這就是任務(wù)調(diào)度,可以通過(guò)休眠狀態(tài)表與掛起狀態(tài)表相與得到任務(wù)就緒狀態(tài)表,在此表中1為就緒態(tài),0為非就緒態(tài)。位數(shù)越高的任務(wù)代表的優(yōu)先級(jí)越高(例如,bit 7所代表任務(wù)的優(yōu)先級(jí)高于bit 6),可以通過(guò)函數(shù)task-Sched()查找就緒狀態(tài)表中位數(shù)最高的1以確定下一個(gè)進(jìn)入運(yùn)行的任務(wù),其實(shí)現(xiàn)如圖2所示??驁D中的變量NextRunningTask做為全局變量代表下一個(gè)即將運(yùn)行的任務(wù)。此外還需按照?qǐng)D1根據(jù)實(shí)際需要對(duì)相關(guān)的任務(wù)進(jìn)行切換。就是通過(guò)程序把休眠狀態(tài)表或掛起狀態(tài)表相應(yīng)的位設(shè)為1或0來(lái)實(shí)現(xiàn)狀態(tài)的轉(zhuǎn)換,以setReady(int i)為例畫(huà)出框圖說(shuō)明如何將掛起態(tài)的任務(wù)轉(zhuǎn)換為就緒態(tài),i代表想要變成就緒態(tài)的任務(wù)號(hào),如圖3所示。此外還有些狀態(tài)轉(zhuǎn)換的函數(shù)與此類似,8051有2個(gè)外部中斷,在中斷中可以調(diào)用這些程序來(lái)改變?nèi)蝿?wù)的狀態(tài),由于篇幅的原因不具體介紹。
運(yùn)行的任務(wù)切換為其它狀態(tài)或其它狀態(tài)切換成運(yùn)行態(tài),不僅僅需要狀態(tài)表的變換,還需要堆棧進(jìn)行上下文切換。所謂的上下文切換是指將當(dāng)前任務(wù)的現(xiàn)場(chǎng)數(shù)據(jù)推入堆棧,將要運(yùn)行任務(wù)的現(xiàn)場(chǎng)數(shù)據(jù)從堆棧里恢復(fù)。根據(jù)不同CPU以及片內(nèi)存儲(chǔ)器大小的差異用不同的方法建立堆棧,一種是為每個(gè)任務(wù)建立大小相同的任務(wù)堆棧,其容量按現(xiàn)場(chǎng)數(shù)據(jù)的最大值計(jì)算。另一種是每個(gè)任務(wù)的堆棧大小根據(jù)實(shí)際需要來(lái)確定。這2種方法都需要把每個(gè)任務(wù)的棧底或棧頂?shù)奈恢帽4嬖诙褩?shù)組stackPos中。針對(duì)2種建立堆棧的方法,其上下文切換的方法也是不同的。
方法1是推入當(dāng)前任務(wù)的現(xiàn)場(chǎng)數(shù)據(jù)后,根據(jù)棧底位置+(要運(yùn)行的任務(wù)號(hào)*每個(gè)任務(wù)堆棧占用的字節(jié)數(shù)),直接找到將要運(yùn)行任務(wù)的堆棧地址,將堆棧指針指向該處即可推出數(shù)據(jù)。該種方法的優(yōu)點(diǎn)是不需要移動(dòng)其它任務(wù)的現(xiàn)場(chǎng)數(shù)據(jù),其任務(wù)切換較快,缺點(diǎn)也很明顯,內(nèi)存浪費(fèi)較大,所以它比較適合TI的DSP2407等大內(nèi)存系統(tǒng)。
圖2 任務(wù)調(diào)度
圖3 setReady框圖
方法2則將多余的內(nèi)存保存在當(dāng)前任務(wù)堆棧中,推入現(xiàn)場(chǎng)數(shù)據(jù)后,移動(dòng)當(dāng)前任務(wù)與將要運(yùn)行任務(wù)之間所有的現(xiàn)場(chǎng)數(shù)據(jù),使堆棧中的多余空間保存在將要運(yùn)行的任務(wù)上,將堆棧指針指向?qū)⒁\(yùn)行任務(wù)的堆棧[4]。具體實(shí)現(xiàn)如圖 4、圖5所示。由于現(xiàn)場(chǎng)數(shù)據(jù)與CPU的型號(hào)有很大的關(guān)系,所以這一段需要用匯編語(yǔ)言編寫(xiě)[5]。使用方法2時(shí)內(nèi)存利用率較高,但是由于需要移動(dòng)多個(gè)任務(wù)的現(xiàn)場(chǎng)數(shù)據(jù),切換速度較慢,比較適合8051等小內(nèi)存系統(tǒng)。這2種方法在系統(tǒng)中是通過(guò)編譯開(kāi)關(guān)來(lái)實(shí)現(xiàn)選擇編譯的。
圖4 任務(wù)堆棧切換方法1
圖5 任務(wù)堆棧切換方法2
對(duì)于共享設(shè)備與共享資源,信號(hào)量的操作是不可避免的。在進(jìn)入共享資源前,任務(wù)必須獲取一個(gè)信號(hào)量;一旦共享設(shè)備使用完成,那么該設(shè)備必須釋放信號(hào)量[6]。其它想進(jìn)入的任務(wù)必須等待,直到某個(gè)任務(wù)釋放信號(hào)量。在信號(hào)量使用時(shí)經(jīng)常會(huì)遇到優(yōu)先級(jí)反轉(zhuǎn)的問(wèn)題。所謂優(yōu)先級(jí)反轉(zhuǎn)是指高優(yōu)先級(jí)任務(wù)需要等待低優(yōu)先級(jí)任務(wù)釋放資源,而低優(yōu)先級(jí)任務(wù)又正在等待中等優(yōu)先級(jí)任務(wù)的現(xiàn)象叫做優(yōu)先級(jí)反轉(zhuǎn)。
舉個(gè)例子,任務(wù)1優(yōu)先級(jí)高于任務(wù)2,任務(wù)2優(yōu)先級(jí)高于任務(wù)3。任務(wù)1和任務(wù)2處于掛起狀態(tài),等待某一事件的發(fā)生,任務(wù)3正在運(yùn)行。此時(shí),任務(wù)3要使用其共享資源。使用共享資源之前,首先必須得到該資源的信號(hào)量(Semaphore)。任務(wù)3得到了該信號(hào)量,并開(kāi)始使用該共享資源。由于任務(wù)1優(yōu)先級(jí)高,它等待的事件到來(lái)之后剝奪了任務(wù)3的CPU使用權(quán),任務(wù)1開(kāi)始運(yùn)行。運(yùn)行過(guò)程中任務(wù)1也要使用任務(wù)3正在使用著的資源,由于該資源的信號(hào)量還被任務(wù)3占用著,任務(wù)1只能進(jìn)入掛起狀態(tài),等待任務(wù)3釋放該信號(hào)量,任務(wù)3得以繼續(xù)運(yùn)行。
由于任務(wù)2的優(yōu)先級(jí)高于任務(wù)3,當(dāng)任務(wù)2等待的事件發(fā)生后,任務(wù)2剝奪了任務(wù)3的CPU的使用權(quán)并開(kāi)始運(yùn)行,處理它該處理的事件,直到處理完之后將CPU控制權(quán)還給任務(wù)3。
任務(wù)3接著運(yùn)行,直到釋放該共享資源的信號(hào)量。直到此時(shí),實(shí)時(shí)內(nèi)核知道有個(gè)高優(yōu)先級(jí)的任務(wù)在等待這個(gè)信號(hào)量,內(nèi)核做任務(wù)切換,使任務(wù)1得到該信號(hào)量并接著運(yùn)行,在這種情況下,任務(wù)1優(yōu)先級(jí)實(shí)際降到了任務(wù)3的優(yōu)先級(jí)水平。因?yàn)槿蝿?wù)1要等,等到任務(wù)3釋放占有的共享資源。由于任務(wù)2剝奪任務(wù)3的CPU使用權(quán),使任務(wù)1的狀況更加惡化,任務(wù)2使任務(wù)1增加了額外的延遲時(shí)間。任務(wù)1和任務(wù)2的優(yōu)先級(jí)發(fā)生了反轉(zhuǎn)[1]。
為解決此問(wèn)題,可采用優(yōu)先級(jí)繼承算法來(lái)實(shí)現(xiàn),就是將任務(wù)3的優(yōu)先級(jí)提高到任務(wù)1來(lái)。由于是在單片機(jī)上運(yùn)行這個(gè)操作系統(tǒng),為減少代碼量及其內(nèi)存,通過(guò)修改備份后的就緒狀態(tài)表與堆棧位置數(shù)組stackPos來(lái)實(shí)現(xiàn),就是當(dāng)一個(gè)任務(wù)因?yàn)樾盘?hào)量進(jìn)入掛起狀態(tài)時(shí),檢測(cè)是否有低優(yōu)先級(jí)的任務(wù)正在占用該信號(hào)量,如果有修改堆棧位置數(shù)組中的高優(yōu)先級(jí)任務(wù)的堆棧位置指向,使其指向占用該資源低優(yōu)先級(jí)任務(wù)的堆棧位置,實(shí)現(xiàn)優(yōu)先級(jí)繼承,在任務(wù)運(yùn)行完后通過(guò)信號(hào)量釋放來(lái)恢復(fù)原有的堆棧位置數(shù)組,恢復(fù)原有的優(yōu)先級(jí),重新設(shè)定休眠狀態(tài)表與就緒狀態(tài)表,具體如圖6、圖7所示,該種方法只需占用極少的內(nèi)存,很適合在8051這種小內(nèi)存的系統(tǒng)上運(yùn)行。另外在該操作系統(tǒng)中各個(gè)任務(wù)間的通信可通過(guò)信息隊(duì)列或郵箱來(lái)實(shí)現(xiàn),這與信號(hào)量的實(shí)現(xiàn)相似。需要指出的是上述介紹的都是操作系統(tǒng)的臨界代碼,進(jìn)入臨界代碼需要關(guān)中斷,完成臨界代碼后再打開(kāi)中斷,只有這樣才能保證系統(tǒng)的正常運(yùn)行[7,8]。
圖6 帶有優(yōu)先級(jí)繼承的信號(hào)量掛起
圖7 信號(hào)量釋放
搶先式內(nèi)核需要可重入的函數(shù),所以在編寫(xiě)前,還需要了解編譯環(huán)境是否易于產(chǎn)生可重入函數(shù)。如果采用8051的C語(yǔ)言編譯器KEil C作為編譯環(huán)境,一般情況下會(huì)產(chǎn)生不可重入函數(shù),因?yàn)樗丫植孔兞恳卜旁谙到y(tǒng)內(nèi)存的固定位置中,就相當(dāng)于全局變量一樣,在這種情況下必須采取一些方法來(lái)產(chǎn)生一個(gè)可重入函數(shù),例如減少函數(shù)自變量個(gè)數(shù),使得KEil C將每個(gè)自變量放入寄存器,而不是放在內(nèi)存中來(lái)產(chǎn)生可重入函數(shù)。
在8051系列單片機(jī)上使用該操作系統(tǒng)對(duì)房間溫度濕度控制系統(tǒng)編寫(xiě)程序。該控制系統(tǒng)通過(guò)溫度與濕度傳感器檢測(cè)房間中的實(shí)際溫度,在出現(xiàn)偏差時(shí)通過(guò)空調(diào)與加濕機(jī)來(lái)保持房間溫度濕度恒定,并顯示溫度與濕度的實(shí)際值。在此系統(tǒng)中將溫度顯示、濕度顯示、溫度控制與濕度控制分別作為控制任務(wù),按照優(yōu)先級(jí)從高到低寫(xiě)入到數(shù)組函數(shù)中。由于其中2個(gè)任務(wù)共用一個(gè)顯示,所以必須使用信號(hào)量函數(shù)。通過(guò)在上下文切換與信號(hào)量函數(shù)中設(shè)置斷點(diǎn),觀察掛起狀態(tài)表與將要運(yùn)行任務(wù)等變量,確定各個(gè)任務(wù),可根據(jù)優(yōu)先級(jí)的高低自動(dòng)進(jìn)行切換,堆棧中的現(xiàn)場(chǎng)數(shù)據(jù)推入與推出正確,溫度與濕度可用7段代碼依次顯示。另外為測(cè)試優(yōu)先級(jí)反轉(zhuǎn),修改了程序,將濕度顯示與溫度控制對(duì)調(diào)。在濕度顯示時(shí)觸發(fā)溫度控制使其運(yùn)行,通過(guò)在信號(hào)量等待函數(shù)中設(shè)置斷點(diǎn),這時(shí)觀察到優(yōu)先級(jí)發(fā)生轉(zhuǎn)換,證明了優(yōu)先級(jí)繼承算法使任務(wù)的實(shí)時(shí)響應(yīng)獲得了極大的提高。由于該操作系統(tǒng)大小不到3 k字節(jié),對(duì)硬件的要求極低,占用的系統(tǒng)資源較少,使該控制系統(tǒng)能夠順利運(yùn)行,最關(guān)鍵的是由于在設(shè)計(jì)過(guò)程中使用RTOS,設(shè)計(jì)調(diào)試時(shí)間由原來(lái)的 1個(gè)月縮短為1周,提高了設(shè)計(jì)效率。
[1]Labrosse J J.嵌入式實(shí)時(shí)操作系統(tǒng)μ C/OS[M].邵貝貝,譯.北京:北京航天航空大學(xué)出版社,2003:120-125.
[2]譚浩強(qiáng).C程序設(shè)計(jì)[M].第2版.北京:清華大學(xué)出版社,2003:102-105.
[3]馬忠梅,籍順心,張 凱,等.單片機(jī)的C語(yǔ)言應(yīng)用程序設(shè)計(jì)[M].第 3版.北京:北京航空航天大學(xué)出版社,2003:45-47.
[4]彭良清.μ C/OS-II任務(wù)堆棧處理的一種改進(jìn)方法[J].單片機(jī)與嵌入式系統(tǒng)應(yīng)用,2008,(5):115-120.
[5]卡馬爾.嵌入式體系結(jié)構(gòu)編程與設(shè)計(jì)[M].北京:清華大學(xué)出版社,2005:89-98.
[6]Allworth S T.Introduction to real-time software desig n[M].New York :Springer-Verlag,1981:31-32.
[7]Douglas C.Operating-system design:the XINU approach[M].Englewood Cliffs,New Jersey:Prentice-Hall,1984:6-9.
[8]Wood M,Barrett T.A real-time primer[J].Embedded Systems Prog ramming,1990,3(2):20-28.