Keil C51處理可重入函數(shù)問(wèn)題的探討
在程序設(shè)計(jì)中,變量具體可以分為四種類(lèi)型:全局變量、靜態(tài)全局變量、局部變量、靜態(tài)局部變量。這幾種變量類(lèi)型對(duì)函數(shù)的可重入產(chǎn)生的重大的影響,因?yàn)椴煌木幾g器采用不同的策略。
針對(duì)51的存儲(chǔ)區(qū)有限,keil c51因此有了覆蓋和共享的處理方法。
共享:共享是針對(duì)全局變量或靜態(tài)變量而言的,對(duì)全局變量定義后就對(duì)其分配了內(nèi)存,其他變量不會(huì)覆蓋這一地址,在任何函數(shù)或者程序中都可以共享該變量的內(nèi)存。
覆蓋:如果一個(gè)程序不再被調(diào)用,也不由其他的程序調(diào)用,在其他的程序運(yùn)行之前程序也不在運(yùn)行,那么這個(gè)程序的局部變量可以放在與其他的程序完全相同的RAM空間,這就是覆蓋。
所以說(shuō)C51編譯器并不是真正的C編譯器。
先說(shuō)一下keilc51的“覆蓋技術(shù)”。
1.局部變量存儲(chǔ)在全局RAM空間(不考慮擴(kuò)展外部存儲(chǔ)器的情況);
2.在編譯鏈接時(shí),即已經(jīng)完成局部變量的定位;
3.如果各函數(shù)之間沒(méi)有直接或間接的調(diào)用關(guān)系,則其局部變量空間便可覆蓋。
在單任務(wù)編程下,重入似乎不是個(gè)問(wèn)題,因?yàn)楹瘮?shù)會(huì)一直執(zhí)行下去,就算被中斷打斷,中斷服務(wù)程序保存寄存器,中斷執(zhí)行完畢后又恢復(fù)環(huán)境(注意到keil c51默認(rèn)這樣處理:中斷中局部變量會(huì)采用靜態(tài)分配內(nèi)存的方式,不會(huì)與函數(shù)的局部變量覆蓋),函數(shù)不會(huì)存在重不重入的問(wèn)題。
在實(shí)時(shí)操作系統(tǒng)下,就必須考慮函數(shù)的重不重入了。
先考慮一下可重入函數(shù)與不可重入函數(shù)的具體區(qū)別。在實(shí)時(shí)操作系統(tǒng)下,二者的區(qū)別并不是執(zhí)行過(guò)程中能否被打斷執(zhí)行??芍厝牒瘮?shù)在執(zhí)行過(guò)程中可以被打斷,內(nèi)核啟動(dòng)另一個(gè)任務(wù),該任務(wù)再次調(diào)用該函數(shù)。不可重入函數(shù)在執(zhí)行過(guò)程中可以被打斷,但在另一個(gè)任務(wù)中不得在調(diào)用這個(gè)函數(shù),除非再次回到原來(lái)的任務(wù)將這個(gè)函數(shù)執(zhí)行完成。
再來(lái)考慮可重入函數(shù)與不可重入函數(shù)的實(shí)現(xiàn):因?yàn)槿肿兞?,靜態(tài)全局變量,靜態(tài)局部變量會(huì)分配到固定的內(nèi)存地址,這些量在可重入方面只需考慮保護(hù),問(wèn)題的關(guān)鍵在于動(dòng)態(tài)局部變量的處理,如互相覆蓋和入棧。
可重入函數(shù):首先是據(jù)絕對(duì)意義上的可重入函數(shù),函數(shù)不使用全局變量,只使用存放在寄存器的局部變量。這種函數(shù)任意時(shí)刻都可以被打斷,或再次調(diào)用,應(yīng)為函數(shù)的局部變量在被中斷時(shí)全部被入棧了,等到重新啟用時(shí)又全部出棧了,例如函數(shù)
unsignecharMin(unsignedcharx1,unsignedcharx2){if(x1>x2){x1=x2;}returnx1;}
另一種函數(shù):不使用全局變量,局部變量部分存放在寄存器,部分存放在固定內(nèi)存地址(哪怕靜態(tài)化,獨(dú)享該內(nèi)存),他總是不可重入的,分析如下:靜態(tài)的局部變量a,在第一進(jìn)入函數(shù)后改變,中斷第二次進(jìn)入又改變,回到第一調(diào)用時(shí)可能就改變了,存在Bug,不可重入。
第二種可重入函數(shù):函數(shù)使用全部變量 ,只使用存放在寄存器的局部變量(keil c51盡量將局部變量保存在寄存器)。 這種函數(shù)常常是系統(tǒng)函數(shù),使用臨界區(qū)代碼。對(duì)起到標(biāo)志作用的全局變量進(jìn)行操作時(shí)要關(guān)中斷,防止中斷打斷破壞,處理完畢后再開(kāi)中斷。這其實(shí)已經(jīng)不是絕對(duì)意義上的可重入函數(shù),但局部變量通過(guò)寄存器存放可以避免修改其他函數(shù)的局部變量。
第三種可重入函數(shù):函數(shù)使用全局變量,局部變量部分存放在寄存器,部分存放在固定內(nèi)存地址(因?yàn)榫植孔兞窟^(guò)多)。這種函數(shù)跟第二種很相似,不是真正意義上的可重入。進(jìn)入函數(shù)必須關(guān)中斷,出去在開(kāi)中斷。值得注意的是:存放在固定內(nèi)存地址的局部變量需要聲明為靜態(tài)以獨(dú)享該內(nèi)存,這樣做的目的并不是怕其他函數(shù)其他破壞其局部變量(關(guān)中斷不可能被破壞),而是怕破壞其他函數(shù)的局部變量,因?yàn)槠渌瘮?shù)若有局部變量和其共享一個(gè)內(nèi)存空間,執(zhí)行其他函數(shù)→該函數(shù)→其他函數(shù),可能導(dǎo)致其他函數(shù)局部變量被破壞。
不可重入函數(shù)(前面已經(jīng)說(shuō)明這個(gè)函數(shù)在執(zhí)行過(guò)程中若被打斷,不會(huì)再次調(diào)用,即不可重入)
第一種函數(shù):不使用全局變量,局部變量部分存放在寄存器,部分存放在固定內(nèi)存地址(若都存放在寄存器就是可重入函數(shù)了)。 這種函數(shù)不可重入,但存放在固定內(nèi)存地址的局部變量應(yīng)該防止互相覆蓋,具體原因下面分析。
第二種函數(shù): 不使用全局變量,局部變量都存放在固定內(nèi)存地址,存放在固定內(nèi)存地址的局部變量應(yīng)該防止互相覆蓋,具體原因下面分析。不過(guò)這種函數(shù)很少見(jiàn),因?yàn)閗eil c51先考慮把局部變量存放在寄存器。
第三種函數(shù): 使用全局變量,局部變量部分存放在寄存器。進(jìn)入函數(shù)不需要關(guān)中斷保護(hù)全局變量,因?yàn)檫@種函數(shù)大多是用戶函數(shù),全局變量大多不是起標(biāo)志作用的,一邊是傳遞數(shù)據(jù)的,即一般是一個(gè)函數(shù)修改,一個(gè)函數(shù)讀取。
第四種函數(shù):使用全局變量,局部變量部分存放在寄存器,部分存放在固定內(nèi)存地址,存放在固定內(nèi)存地址的局部變量應(yīng)該防止互相覆蓋。跟第三種類(lèi)似。
還有一種函數(shù): 使用全局變量,局部變量都存放在固定內(nèi)存地址。跟第二種一樣少見(jiàn),都不考慮。
分析不可重入函數(shù)存放在固定地址的局部變量不能相互覆蓋的問(wèn)題:
Keil進(jìn)行以下處理:如果各函數(shù)之間沒(méi)有直接或間接的調(diào)用關(guān)系,則其局部變量空間便可覆蓋。
任務(wù)的基本結(jié)構(gòu)與模式:
voldTaskA(void*ptr){UINT8vaL_a;//其他一些變量定義do{//實(shí)際的用戶任務(wù)處理代碼}while(1);}voidTaskB(void*ptr){UINT8vaL_b;//其他一些變量定義do{Funcl();//其他實(shí)際的用戶任務(wù)處理代碼}while(1);}voidFuncl(){UlNT8val_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)亦然。另一方面,F(xiàn)uncl()與TaskB有直接的調(diào)用關(guān)系,因而其局部變量val_fa與val_b不會(huì)被互相覆蓋,但也不能保證其局部變量val_fa不會(huì)與TaskA或其他任務(wù)的局部變量形成可覆蓋關(guān)系。
所以得到以下結(jié)論:存在直接或間接調(diào)用關(guān)系的函數(shù)局部變量不會(huì)覆蓋,不存在調(diào)用關(guān)系的函數(shù)局部變量又不能覆蓋。
怎么辦?
第一種解決方案:我們只能將非重入函數(shù)中不是存放在寄存器中的局部變量全部靜態(tài)化存放在固定內(nèi)存地址,這樣內(nèi)存開(kāi)銷(xiāo)就會(huì)很大,但要保證程序運(yùn)行的正確性,只能這么做。
第二種:局部變量覆蓋,通過(guò)進(jìn)入程序就關(guān)中斷進(jìn)行保護(hù),這樣就不會(huì)被打斷了,但這樣做系統(tǒng)的實(shí)時(shí)性就會(huì)降低很多。 現(xiàn)實(shí)情況是根據(jù)具體的情況進(jìn)行決定,具體問(wèn)題具體分析。