<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>

新聞中心

EEPW首頁(yè) > 嵌入式系統 > 設計應用 > KEIL C51可重入函數及模擬棧淺析

KEIL C51可重入函數及模擬棧淺析

作者: 時(shí)間:2016-11-26 來(lái)源:網(wǎng)絡(luò ) 收藏
重入函數,又叫再入函數,是一種可以在函數體內直接或間接調用其自身的一種函數。

再入函數可被遞歸調用,無(wú)論何時(shí),包括中斷函數在內的任何函數都可以調入。再入函數在C51編譯時(shí)使用的是模擬棧。

本文引用地址:http://dyxdggzs.com/article/201611/321761.htm

函數說(shuō)明:函數名(形式參數表) reentrant

注意事項:

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)于可重入函數(可再入函數)和模擬堆棧(仿真堆棧)

“可重入函數可以被一個(gè)以上的任務(wù)調用,而不必擔心數據被破壞??芍厝牒瘮等魏螘r(shí)候都可以被中斷,一段時(shí)間以后又可以運行,而相應的數據不會(huì )丟失。”(摘自嵌入式實(shí)時(shí)操作系統uC/OS-II)

在理解上述概念之前,必須先說(shuō)一下keil c51的“覆蓋技術(shù)”。(采用該技術(shù)的原因請看附錄中一網(wǎng)友的解釋?zhuān)?/p>

(1)局部變量存儲在全局RAM空間(不考慮擴展外部存儲器的情況);

(2)在編譯鏈接時(shí),即已經(jīng)完成局部變量的定位;

(3)如果各函數之間沒(méi)有直接或間接的調用關(guān)系,則其局部變量空間便可覆蓋。

正是由于以上的原因,在Keil C51環(huán)境下,純粹的函數如果不加處理(如增加一個(gè)模擬棧),是無(wú)法重入的。舉個(gè)例子:

在上面的代碼中,TaskA與TaskB并不存在直接或間接的調用關(guān)系,因而它們的局部變量a與b便是可以被互相覆蓋的,即它們可能都被定位于某一個(gè)相同的RAM空間。這樣,當TaskA運行一段時(shí)間,改變了a后,TaskB取得CPU控制權并運行時(shí),便可能會(huì )改變b。由于a和b指向相同的RAM空間,導致TaskA重新取得CPU控制權時(shí),a的值已經(jīng)改變,從而導致程序運行不正確,反過(guò)來(lái)亦然。另一方面,func()與TaskB有直接的調用關(guān)系,因而其局部變量b與c不會(huì )被互相覆蓋,但也不能保證func的局部變量c不會(huì )與TaskA或其他任務(wù)的局部變量形成可覆蓋關(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)如果一個(gè)函數func1調用另一個(gè)函數func2,那么func1,func2的局部變量根本就不能是同一塊內存.C51還是要為他們分配不同的RAM.這跟使用堆棧相比,節約不了內存.

  2)如果func1,func2不是在一個(gè)調用鏈上,那么C51可以通過(guò)覆蓋分析,讓它們的局部變量共享相同的內存地址.但這樣也不會(huì )比使用堆棧節約內存.因為既然它們是在不同的調用鏈上,那么當其中一個(gè)函數運行時(shí),那么另外一個(gè)函數必然不在其生命期內,它所占用的堆棧也已釋放,歸還給系統.

  真實(shí)的原因(C51使用覆蓋段作為局部變量的存放地的原因)是:

  51的指令系統沒(méi)有一個(gè)有效的相對尋址(變址尋址)的指令,這使得使用堆棧作為變量的代價(jià)太過(guò)昂貴.

  使用堆棧存放變量的一般做法是:

  進(jìn)入函數時(shí),保留一段堆??臻g,作為變量的存放空間,用一個(gè)可作為基址尋址的寄存器指向這個(gè)空間,通過(guò)加上一個(gè)偏移量,就可以訪(fǎng)問(wèn)不同的變量了.

  例如:

  MOV EAX, [EBP + 14];X86指令

  LDR R0, [R12, #14];ARM指令

  都可以很好的解決這個(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用來(lái)解決函數重入的問(wèn)題。



關(guān)鍵詞: KEILC51可重入函數模擬

評論


技術(shù)專(zhuān)區

關(guān)閉
国产精品自在自线亚洲|国产精品无圣光一区二区|国产日产欧洲无码视频|久久久一本精品99久久K精品66|欧美人与动牲交片免费播放
<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>