如何構(gòu)造一個(gè)51單片機(jī)的實(shí)時(shí)操作系統(tǒng)
從Keil C51的內(nèi)存空間管理方式入手,著重討論實(shí)時(shí)操作系統(tǒng)在任務(wù)調(diào)度時(shí)的重入問(wèn)題,分析一些解決重入的基本方式與方法:分析實(shí)時(shí)操作系統(tǒng)任務(wù)調(diào)度的占先性,提出非占先的任務(wù)調(diào)度是能更適合于Keil C51的一種調(diào)度方式。為此,構(gòu)造這一實(shí)時(shí)操作系統(tǒng),并有針對(duì)性地介紹此系統(tǒng)的堆管理方法、任務(wù)的建立以廈任務(wù)的切換等。
目前,大多數(shù)的產(chǎn)品開(kāi)發(fā)是在基于一些小容量的單片機(jī)上進(jìn)行的。51系列單片機(jī),是我國(guó)目前使用最多的單片機(jī)系列之一,有非常廣大的應(yīng)用環(huán)境與前景,多年來(lái)的資源積累,使51系列單片機(jī)仍是許多開(kāi)發(fā)者的首選。針對(duì)這種情況,近幾年涌現(xiàn)出許多基于51內(nèi)核的擴(kuò)展芯片,功能越來(lái)越齊全,速度越來(lái)越快,也從一個(gè)側(cè)面說(shuō)明了51系列單片機(jī)在國(guó)內(nèi)的生命力。
多年來(lái)我們一直想找一個(gè)合適的實(shí)時(shí)操作系統(tǒng),作為自己的開(kāi)發(fā)基礎(chǔ)。根據(jù)開(kāi)發(fā)需求,整合一些常用的嵌入式構(gòu)件,以節(jié)約開(kāi)發(fā)時(shí)間,盡最大可能地減少開(kāi)發(fā)工作量;另外,要求這個(gè)實(shí)時(shí)操作系統(tǒng)能非常容易地嵌入到小容量的芯片中。畢竟,大系統(tǒng)是少數(shù)的,而小應(yīng)用是多數(shù)而廣泛的。顯而易見(jiàn),μC/OS—II是不太適合于以上要求的,而Keil C所帶的RTX Tiny不帶源代碼,不具透明性,至于其FULL版本就更不用說(shuō)了。
1 KeiI C51與重入問(wèn)題
說(shuō)到實(shí)時(shí)操作系統(tǒng),就不能不考慮重入問(wèn)題。對(duì)于PC機(jī)這樣的大內(nèi)存處理器而言,這似乎并不是一個(gè)很麻煩的問(wèn)題,借用μC/OS—II RTOS的說(shuō)法,即要求在重入的函數(shù)內(nèi),使用局部變量。但5l系列單片機(jī)堆??臻g很小,僅局限在256字節(jié)之內(nèi),無(wú)法為每個(gè)函數(shù)都分配一個(gè)局部堆空間。正是由于這個(gè)原因,Keil C51使用了所謂的可覆蓋技術(shù):
①局部變量存儲(chǔ)在全局RAM空間(不考慮擴(kuò)展外部存儲(chǔ)器的情況);
②在編譯鏈接時(shí),即已經(jīng)完成局部變量的定位;
③如果各函數(shù)之間沒(méi)有直接或間接的調(diào)用關(guān)系,則其局部變量空間便可覆蓋。
正是由于以上的原因,在Keil C51環(huán)境下,純粹的函數(shù)如果不加處理(如增加一個(gè)模擬棧),是無(wú)法重人的。那么在Keil C5l環(huán)境下,如何使其函數(shù)具有可重人性呢?下面分析在實(shí)時(shí)操作系統(tǒng)下面,任務(wù)的基本結(jié)構(gòu)與模式:
vold TaskA(void*ptr){
UINT8 vaL_a;
//其他一些變量定義
do{
//實(shí)際的用戶任務(wù)處理代碼
}while(1);
}
void TaskB(void*ptr){
UINT8 vaLb;
//其他一些變量定義
do{
Funcl();
//其他實(shí)際的用戶任務(wù)處理代碼
)while(1);
void Funcl(){
UlNT8 v al_fa;
//其他變量的定義
//函數(shù)的處理代碼
}
在上面的代碼中,TaskA與TaskB并不存在直接或間接的調(diào)用關(guān)系,因而其局部變量val_a與val_b便是可以被互相覆蓋的,即其可能都被定位于某一個(gè)相同的RAM空間。這樣,當(dāng)TaskA運(yùn)行一段時(shí)間,改變了val_a后,TaskB取得CPU控制權(quán)并運(yùn)行時(shí),便可能會(huì)改變val_b。由于其指向相同的RAM空間,導(dǎo)致TaskA重新取得CPU控制權(quán)時(shí),val—a的值已經(jīng)改變,從而導(dǎo)致程序運(yùn)行不正確,反過(guò)來(lái)亦然。另一方面,Funcl()與TaskB有直接的調(diào)用關(guān)系,因而其局部變量val_fa與val_b不會(huì)被互相覆蓋,但也不能保證其局部變量val_fa不會(huì)與TaskA或其他任務(wù)的局部變量形成可覆蓋關(guān)系。
將val_a、val_b以及val_fa等局部變量定義為靜態(tài)變量(加上static指示符)可以解決這一問(wèn)題。但問(wèn)題是,定義大量的static類型變量,將導(dǎo)致RAM空間的大量占用,有可能直接導(dǎo)致RAM空間不夠用。尤其是在一些小容量的單片機(jī)內(nèi),一般只有128或256字節(jié),大量的靜態(tài)變量定義,在如此小的RAM資源狀況下顯然就不太合適了。由此而有了另一種的解決方法,如下代碼所示:
void TaskC(void){
UINT8 x,v;
whlk(1){
OS_ENTER_CRITICAL();
x=GetX(); (1)
y=GetY(); (2)
//任務(wù)的其他代碼
OS_EXIT_CRITICAL(); (3)
0SSleep(100); (4)
}
}
以上代碼TaskC中使用了臨界保護(hù)的方法來(lái)保護(hù)代碼不被中斷占先,確實(shí)有效地解決了RAM空間太小,不宜大量定義靜態(tài)變量的問(wèn)題。然而如果每個(gè)任務(wù)都采用此種結(jié)構(gòu),任務(wù)一開(kāi)始,就關(guān)閉中斷,將使實(shí)時(shí)性得不到保證。事實(shí)證明,這種延時(shí)是相當(dāng)可觀的。用一個(gè)實(shí)例來(lái)說(shuō)明,如果想在系統(tǒng)中使用一個(gè)動(dòng)態(tài)刷新的LED顯示器,就難以保證顯示的穩(wěn)定與連續(xù),哪怕在系統(tǒng)中是使用一個(gè)單獨(dú)的定時(shí)器來(lái)做這一工作(進(jìn)入臨界區(qū)后,EA=0)。其次,這種結(jié)構(gòu)事實(shí)上將占先的任務(wù)調(diào)度轉(zhuǎn)化為非占先的任務(wù)調(diào)度。實(shí)際上如果在(3)與(4)之間沒(méi)有碰巧發(fā)生中斷并導(dǎo)致一個(gè)任務(wù)調(diào)度,那就可以理解為是任務(wù)主動(dòng)放棄CPU的控制。如果在(3)和(4)之間碰巧產(chǎn)生了一個(gè)中斷并導(dǎo)致了一個(gè)任務(wù)調(diào)度,只是執(zhí)行了一次多余的任務(wù)調(diào)度而已,而且并不希望在(3)之后發(fā)生2次甚至多次的任務(wù)調(diào)度,相信讀者也有這一愿望。
除此之外,還可以發(fā)現(xiàn)任務(wù)的一個(gè)特點(diǎn):當(dāng)任務(wù)從(1)重新開(kāi)始時(shí),局部變量x和y是一個(gè)什么值并不在乎,即x和y即使在(3)之后改變了,也已經(jīng)不再重要,不會(huì)影響程序的正確性。其實(shí)這一特點(diǎn)也是大部分任務(wù),至少是太部分任務(wù)的大部分局部變量的一個(gè)共性——如果任務(wù)在整個(gè)執(zhí)行過(guò)程中,不會(huì)(被占先)放棄CPU控制權(quán),則其局部變量大多數(shù)并不需要進(jìn)行特別的保護(hù),即其作用域只是任務(wù)的當(dāng)次執(zhí)行,針對(duì)上面的代碼,就是臨界保護(hù)區(qū)內(nèi)的代碼區(qū)域。
評(píng)論