KEIL C51可重入函數及模擬棧淺析
1、再入函數不能傳遞bit類(lèi)型參數。
2、與PL/M51兼容的函數不能具有reentrant屬性,這樣也不能調用再入函數。
3、在編譯時(shí):再入函數建立的是模擬堆棧區,small模式下模擬堆棧區位于idata區,compact模式下模擬堆棧區位于pdata區,large模式下模擬堆棧區位于xdata區。
4、在同一程序中可以定義和使用不同存儲器模式的再入函數,任意模式的再入函數不能調用不同存儲器模式的再入函數,但可以調用普通函數。
5、實(shí)際參數可以傳遞給間接調用的再入函數。無(wú)再入屬性的間接調用函數不能包含調用參數。
—————————————————————————————————-----
KEIL C51可重入函數及模擬棧淺析
摘要:本文較詳細的介紹了keil c51可再入函數和模擬堆棧的一些概念和實(shí)現原理,通過(guò)一個(gè)簡(jiǎn)單的程序來(lái)剖析keil c51在大存儲模式下可重入函數的調用過(guò)程,希望能為keil c51和在51系列單片機上移植嵌入式實(shí)時(shí)操作系統的初學(xué)者提供一些幫助。
1、關(guān)于可重入函數(可再入函數)和模擬堆棧(仿真堆棧)
(1)局部變量存儲在全局RAM空間(不考慮擴展外部存儲器的情況);
(2)在編譯鏈接時(shí),即已經(jīng)完成局部變量的定位;
(3)如果各函數之間沒(méi)有直接或間接的調用關(guān)系,則其局部變量空間便可覆蓋。
根據上述分析我們很容易就能夠判斷出TaskA和TaskB這兩個(gè)函數是不可重入的(當然,func也不可重入)。那么如何讓函數成為可重入函數呢?C51編譯器采用了一個(gè)擴展關(guān)鍵字reentrant作為定義函數時(shí)的選項,需要將一個(gè)函數定義為可重入函數時(shí),只要在函數后面加上關(guān)鍵字reentrant即可。
與非可重入函數的參數傳遞和局部變量的存儲分配方法不同,C51編譯器為可重入函數生成一個(gè)模擬棧(相對于系統堆?;蚴怯布褩?lái)說(shuō)),通過(guò)這個(gè)模擬棧來(lái)完成參數傳遞和存放局部變量。模擬棧以全局變量?C_IBP、?C_PBP和?C_XBP作為棧指針(系統堆棧棧頂指針為SP),這些變量定義在DATA地址空間,并且可在文件startup.a51中進(jìn)行初始化。根據編譯時(shí)采用的存儲器模式,模擬棧區可位于內部(IDATA)或外部(PDATA或XDATA)存儲器中。如表1所示:
表1
注意:51系列單片機的系統堆棧(也叫硬件堆?;虺R帡#┛偸俏挥趦炔繑祿鎯ζ髦校⊿P為 8位寄存器,只能指向內部),而且是“向上生長(cháng)”型的(從低地址向高地址),而模擬棧是“向下生長(cháng)”型的。
2、可重入函數參數傳遞過(guò)程剖析
在進(jìn)入剖析之前,先簡(jiǎn)單講講c51函數調用時(shí)參數是如何傳遞的。簡(jiǎn)單來(lái)說(shuō),參數主要是通過(guò)寄存器R1~R7來(lái)傳遞的,如果在調用時(shí),參數無(wú)寄存器可用或是采用了編譯控制指令“NOREGPARMS”,則參數的傳遞將發(fā)生在固定的存儲器區域,該存儲器區域稱(chēng)為參數傳遞段,其地址空間取決于編譯時(shí)所選擇的存儲器模式。利用51單片機的工作寄存器最多傳遞3個(gè)參數,如表2所示。
表二
舉兩個(gè)例子:
func1(int a):“a”是第一個(gè)參數,在R6,R7中傳遞;
func2(int b,int c, int *d):“b”在R6,R7中傳遞,“c”在R4,R5中傳遞,“d”則在R1,R2,R3中傳遞。
至于函數的返回值通過(guò)哪些寄存器或是什么方法傳遞這里就不說(shuō)了,大家可以看看c51的相關(guān)文檔或是書(shū)籍。
好了,接下來(lái)我們開(kāi)始剖析一個(gè)簡(jiǎn)單的程序,代碼如下:
程序很簡(jiǎn)單,廢話(huà)少說(shuō),下面跟我一起看看c51翻譯成的匯編語(yǔ)言是什么樣子的(大存儲模式下large XDATA)。
說(shuō)明:模擬棧指針最初在startup.a51中初始化為0xFFFF+1;由以上匯編代碼可以看出參數是從右往左掃描的。
接下來(lái)看看fun的匯編代碼:(很長(cháng),大家耐心看吧,有些可以跳過(guò)的)
說(shuō)明:棧結構如下
接下來(lái)說(shuō)明兩個(gè)重點(diǎn)子函數C_ADDXBP和C_XBPOFF
終于到尾聲了,最后重點(diǎn)說(shuō)明啦~~~
是向下生長(cháng)的,C_XBP最初等于0xffff+1,那么請看下面這句
其實(shí)是這樣:加0xffff相當與減1,加0xfffe相當與減2,加0xfffd相當于減3。。。。。。為啥,就不用說(shuō)了吧:)
結束語(yǔ):
經(jīng)過(guò)了幾天的研究,終于寫(xiě)了個(gè)總結報告,算是自己的一點(diǎn)小小成就吧,錯誤之處在所難免,希望能夠同大家一起討論問(wèn)題,共同進(jìn)步。
附錄:
在其它環(huán)境下(比如PC,比如ARM),函數重入的問(wèn)題一般不是要特別注意的問(wèn)題。只要你沒(méi)有使用static變量,或者指向static變量的指針,一般情況下,函數自然而然地就是可重入的。
但C51不一樣,如果你不特別設計你的函數,它就是不可重入的.
引起這個(gè)差別的原因在于:一般的C編譯器(或者更確切點(diǎn)地說(shuō):基于一般的處理器上的C編譯器),其函數的局部變量是存放于堆棧中的,而C51是存放于一個(gè)可覆蓋的(數據)段中的。
至于C51這樣做的原因,不是象有些人說(shuō)的那樣,為了節約內存.事實(shí)上,這樣做根本節約不了內存.理由如下:
1)
2)
真實(shí)的原因(C51使用覆蓋段作為局部變量的存放地的原因)是:
51的指令系統沒(méi)有一個(gè)有效的相對尋址(變址尋址)的指令,這使得使用堆棧作為變量的代價(jià)太過(guò)昂貴.
使用堆棧存放變量的一般做法是:
進(jìn)入函數時(shí),保留一段堆??臻g,作為變量的存放空間,用一個(gè)可作為基址尋址的寄存器指向這個(gè)空間,通過(guò)加上一個(gè)偏移量,就可以訪(fǎng)問(wèn)不同的變量了.
例如:
MOV EAX, [EBP + 14]
LDR R0, [R12, #14]
都可以很好的解決這個(gè)問(wèn)題。但51缺少這樣的指令.
其實(shí),51中還是有2個(gè)可變址尋址的指令的,但不適合訪(fǎng)問(wèn)堆棧的局部變量這樣的場(chǎng)合.
MOVC A, @A+DPTR
MOVC A, @A+PC
所以,C51有個(gè)特別的關(guān)鍵字: reentrant
評論