寫中斷程序要注意哪些方面?
與每類I/O設(shè)備相關(guān)的進(jìn)程都有一個(gè)靠近內(nèi)存底部的地址,稱作中斷向量。它包括中斷服務(wù)程序的入口地址。
當(dāng)中央處理器正在處理內(nèi)部數(shù)據(jù)時(shí),外界發(fā)生了緊急情況,要求CPU暫停當(dāng)前的工作轉(zhuǎn)去處理這個(gè)緊急事件。處理完畢后,再回到原來(lái)被中斷的地址,繼續(xù)原來(lái)的工作,這樣的過(guò)程稱為中斷。
中斷處理過(guò)程:
(1)保護(hù)被中斷進(jìn)程現(xiàn)場(chǎng)。為了在中斷處理結(jié)束后能夠使進(jìn)程準(zhǔn)確地返回到中斷點(diǎn),系統(tǒng)必須保存當(dāng)前處理機(jī)程序狀態(tài)字PSW和程序計(jì)數(shù)器PC等的值。
(2)分析中斷原因,轉(zhuǎn)去執(zhí)行相應(yīng)的中斷處理程序。在多個(gè)中斷請(qǐng)求同時(shí)發(fā)生時(shí),處理優(yōu)先級(jí)最高的中斷源發(fā)出的中斷請(qǐng)求。
(3)恢復(fù)被中斷進(jìn)程的現(xiàn)場(chǎng),CPU繼續(xù)執(zhí)行原來(lái)被中斷的進(jìn)程。
三個(gè)大注意事項(xiàng)
1、中斷函數(shù)代碼應(yīng)盡量簡(jiǎn)潔。一般不宜在中斷函數(shù)內(nèi)編寫大量復(fù)雜冗長(zhǎng)的代碼;應(yīng)盡量避免在中斷函數(shù)內(nèi)調(diào)用其他自定義函數(shù);
2、盡量避免在中斷內(nèi)調(diào)用數(shù)學(xué)函數(shù)。因?yàn)槟承?shù)學(xué)函數(shù)涉及相關(guān)的庫(kù)函數(shù)調(diào)用和中間變量較多,可能出現(xiàn)交叉調(diào)用。在必須使用數(shù)學(xué)函數(shù)時(shí),可考慮將復(fù)雜的數(shù)學(xué)函數(shù)運(yùn)算任務(wù)交給主程序完成,中斷函數(shù)通過(guò)全局變量引用其結(jié)果;
3、宏的定義與調(diào)用。在中斷函數(shù)中調(diào)用宏,可減少在函數(shù)調(diào)用中壓棧與出棧的開銷。
九個(gè)小注意事項(xiàng)
1、中斷函數(shù)不能進(jìn)行參數(shù)傳遞
2、中斷函數(shù)沒(méi)有返回值
3、在任何情況下都不能直接調(diào)用中斷函數(shù)
4、中斷函數(shù)使用浮點(diǎn)運(yùn)算要保存浮點(diǎn)寄存器的狀態(tài)。
5、如果在中斷函數(shù)中調(diào)用了其它函數(shù),則被調(diào)用函數(shù)所使用的寄存器必須與中斷函數(shù)相同,被調(diào)函數(shù)最好設(shè)置為可重入的。
6、(可忽略)C51編譯器對(duì)中斷函數(shù)編譯時(shí)會(huì)自動(dòng)在程序開始和結(jié)束處加上相應(yīng)的內(nèi)容,具體如下:
在程序開始處對(duì)ACC、B、DPH、DPL和PSW入棧,結(jié)束時(shí)出棧。
中斷函數(shù)未加using n修飾符的,開始時(shí)還要將R0~R1入棧,結(jié)束時(shí)出棧。
如中斷函數(shù)加using n修飾符,則在開始將PSW入棧后還要修改PSW中的工作寄存器組選擇位。
C51編譯器從絕對(duì)地址8m 3處產(chǎn)生一個(gè)中斷向量,其中m為中斷號(hào),也即interrupt后面的數(shù)字。該向量包含一個(gè)到中斷函數(shù)入口地址的絕對(duì)跳轉(zhuǎn)。
7、中斷函數(shù)最好寫在文件的尾部,并且禁止使用extern存儲(chǔ)類型說(shuō)明。防止其它程序調(diào)用。
8、在設(shè)計(jì)中斷時(shí),要注意的是哪些功能應(yīng)該放在中斷程序中,哪些功能應(yīng)該放在主程序中。一般來(lái)說(shuō)中斷服務(wù)程序應(yīng)該做最少量的工作,這樣做有很多好處。
首先系統(tǒng)對(duì)中斷的反應(yīng)面更寬了,有些系統(tǒng)如果丟失中斷或?qū)χ袛喾磻?yīng)太慢將產(chǎn)生十分嚴(yán)重的后果,這時(shí)有充足的時(shí)間等待中斷是十分重要的。
其次它可使中斷服務(wù)程序的結(jié)構(gòu)簡(jiǎn)單,不容易出錯(cuò)。中斷程序中放入的東西越多,他們之間越容易起沖突。簡(jiǎn)化中斷服務(wù)程序意味著軟件中將有更多的代碼段,但可把這些都放入主程序中。
9、中斷服務(wù)程序的設(shè)計(jì)對(duì)系統(tǒng)的成敗有至關(guān)重要的作用,要仔細(xì)考慮各中斷之間的關(guān)系和每個(gè)中斷執(zhí)行的時(shí)間,特別要注意那些對(duì)同一個(gè)數(shù)據(jù)進(jìn)行操作的中斷
舉例說(shuō)明
中斷是嵌入式系統(tǒng)中重要的組成部分,這導(dǎo)致了很多編譯開發(fā)商提供一種擴(kuò)展—讓標(biāo)準(zhǔn)C支持中斷。具代表事實(shí)是,產(chǎn)生了一個(gè)新的關(guān)鍵字 __interrupt。下面的代碼就使用了__interrupt關(guān)鍵字去定義了一個(gè)中斷服務(wù)子程序(ISR),請(qǐng)?jiān)u論一下這段代碼的。
- __interrupt double compute_area (double radius)
- {
- double area = PI * radius * radius;
- printf("\nArea = %f", area);
- return area;
- }
這個(gè)函數(shù)有太多的錯(cuò)誤了:
1) ISR 不能返回一個(gè)值。如果你不懂這個(gè),那么你不會(huì)被雇用的。
2) ISR 不能傳遞參數(shù)。如果你沒(méi)有看到這一點(diǎn),你被雇用的機(jī)會(huì)等同第一項(xiàng)。
3) 在許多的處理器/編譯器中,浮點(diǎn)一般都是不可重入的。有些處理器/編譯器需要讓額處的寄存器入棧,有些處理器/編譯器就是不允許在ISR中做浮點(diǎn)運(yùn)算。此外,ISR應(yīng)該是短而有效率的,在ISR中做浮點(diǎn)運(yùn)算是不明智的。
4) 與第三點(diǎn)一脈相承,printf()經(jīng)常有重入和性能上的問(wèn)題。如果你丟掉了第三和第四點(diǎn),我不會(huì)太為難你的。不用說(shuō),如果你能得到后兩點(diǎn),那么你的被雇用前景越來(lái)越光明了。
解釋重入:
printf()經(jīng)常有重入解釋
不可重入函數(shù)不可以在它還沒(méi)有返回就再次被調(diào)用。例如printf,malloc,free等都是不可重入函數(shù)。因?yàn)橹袛嗫赡茉谌魏螘r(shí)候發(fā)生,例如在printf執(zhí)行過(guò)程中,因此不能在中斷處理函數(shù)里調(diào)用printf,否則printf將會(huì)被重入。
函數(shù)不可重入大多數(shù)是因?yàn)樵诤瘮?shù)中引用了全局變量。例如,printf會(huì)引用全局變量stdout,malloc,free會(huì)引用全局的內(nèi)存分配表。
如果中斷發(fā)生的時(shí)候,當(dāng)運(yùn)行到printf的時(shí)候,假設(shè)發(fā)生了中斷嵌套,而此時(shí)stdout資源被占用,所以第二個(gè)中斷printf等待第一個(gè)中斷的stdout資源釋放,第一個(gè)中斷等待第二個(gè)中斷返回,造成了死鎖。
不可重入函數(shù)指的是該函數(shù)在被調(diào)用還沒(méi)有結(jié)束以前,再次被調(diào)用可能會(huì)產(chǎn)生錯(cuò)誤??芍厝牒瘮?shù)不存在這樣的問(wèn)題。
不可重入函數(shù)在實(shí)現(xiàn)時(shí)候通常使用了全局的資源,在多線程的環(huán)境下,如果沒(méi)有很好的處理數(shù)據(jù)保護(hù)和互斥訪問(wèn),就會(huì)發(fā)生錯(cuò)誤。
常見的不可重入函數(shù)有:
- printf --------引用全局變量stdout
- malloc --------全局內(nèi)存分配表
- free --------全局內(nèi)存分配表
在unix里面通常都有加上_r后綴的同名可重入函數(shù)版本。如果實(shí)在沒(méi)有,不妨在可預(yù)見的發(fā)生錯(cuò)誤的地方嘗試加上保護(hù)鎖同步機(jī)制等等。
下面引用一段別人的解釋:
這主要在多任務(wù)環(huán)境中使用,一個(gè)可重入的函數(shù)簡(jiǎn)單來(lái)說(shuō),就是:可以被中斷的函數(shù)。就是說(shuō),你可以在這個(gè)函數(shù)執(zhí)行的任何時(shí)候中斷他的運(yùn)行,在OS的調(diào)度下去執(zhí)行另外一段代碼而不會(huì)出現(xiàn)什么錯(cuò)誤。而不可重入的函數(shù)由于使用了一些系統(tǒng)資源,比如全局變量區(qū),中斷向量表等等,所以他如果被中斷的話,可能出現(xiàn)問(wèn)題,所以這類函數(shù)是不能運(yùn)行在多任務(wù)環(huán)境下的。
把一個(gè)不可重入函數(shù)變成可重入的唯一方法是用可重入規(guī)則來(lái)重寫他。
其實(shí)很簡(jiǎn)單,只要遵守了幾條很容易理解的規(guī)則,那么寫出來(lái)的函數(shù)就是可重入的:
第一,不要使用全局變量。因?yàn)閯e的代碼很可能覆蓋這些變量值。
第二,在和硬件發(fā)生交互的時(shí)候,切記執(zhí)行類似disinterrupt()之類的操作,就是關(guān)閉硬件中斷。完成交互記得打開中斷,在有些系列上,這叫做“進(jìn)入/退出核心”或者用OS_ENTER_KERNAL/OS_EXIT_KERNAL來(lái)描述。
第三,不能調(diào)用任何不可重入的函數(shù)。
第四,謹(jǐn)慎使用堆棧。最好先在使用前先OS_ENTER_KERNAL。
還有一些規(guī)則,都是很好理解的,總之,時(shí)刻記住一句話:保證中斷是安全的!
通俗的來(lái)講吧:由于中斷是可能隨時(shí)發(fā)生的,斷點(diǎn)位置也是無(wú)法預(yù)期的。所以必須保證每個(gè)函數(shù)都具有不被中斷發(fā)生,壓棧,轉(zhuǎn)向ISR,彈棧后繼續(xù)執(zhí)行影響的穩(wěn)定性。也就是說(shuō)具有不會(huì)被中斷影響的能力。既然有這個(gè)要求,你提供和編寫的每個(gè)函數(shù)就不能拿公共的資源或者是變量來(lái)使用,因?yàn)樵摵瘮?shù)使用的同時(shí),ISR(中斷服務(wù)程序)也可那會(huì)去修改或者是獲取這個(gè)資源,從而有可能使中斷返回之后,這部分公用的資源已經(jīng)面目全非。
滿足下列條件的函數(shù)多數(shù)是不可重入的:
- (1)函數(shù)體內(nèi)使用了靜態(tài)的數(shù)據(jù)結(jié)構(gòu);
- (2)函數(shù)體內(nèi)調(diào)用了malloc()或者free()函數(shù);
- (3)函數(shù)體內(nèi)調(diào)用了標(biāo)準(zhǔn)I/O函數(shù)。
下面舉例加以說(shuō)明。
可重入函數(shù)
- void strcpy(char* lpszDest, char* lpszSrc)
- {
- while(*lpszDest++ = *lpszSrc++);
- *dest=0;
- }
非可重入函數(shù)1
- char cTemp; // 全局變量
- void SwapChar1(char* lpcX, char* lpcY)
- {
- cTemp = *lpcX;
- *lpcX = *lpcY;
- lpcY = cTemp; // 訪問(wèn)了全局變量,在分享內(nèi)存的多個(gè)線程中可能造成問(wèn)題
- }
非可重入函數(shù)2
- void SwapChar2(char* lpcX, char* lpcY)
- {
- static char cTemp; // 靜態(tài)局部變量
- cTemp = *lpcX;
- *lpcX = *lpcY;
- lpcY = cTemp; // 使用了靜態(tài)局部變量,在分享內(nèi)存的多個(gè)線程中可能造成問(wèn)題
- }
如何寫出可重入的函數(shù)?在函數(shù)體內(nèi)不訪問(wèn)那些全局變量,不使用靜態(tài)局部變量,堅(jiān)持只使用局部變量,寫出的函數(shù)就將是可重入的。如果必須訪問(wèn)全局變量,記住利用互斥信號(hào)量來(lái)保護(hù)全局變量。