當(dāng)前位置:首頁 > 單片機 > 單片機
[導(dǎo)讀]51單片機的仿真棧(又叫模擬棧、或者可重入棧)。首先來看,51的系統(tǒng)棧(又叫系統(tǒng)棧,或者硬件棧),就是SP所指向的棧,他是一個滿增棧(注釋1),位于片內(nèi)RAM的128 bytes之中,上電之后系統(tǒng)堆棧指針SP的初值等于多少呢?這

51單片機的仿真棧(又叫模擬棧、或者可重入棧)。

首先來看,51的系統(tǒng)棧(又叫系統(tǒng)棧,或者硬件棧),就是SP所指向的棧,他是一個滿增棧(注釋1),位于片內(nèi)RAM的128 bytes之中,上電之后系統(tǒng)堆棧指針SP的初值等于多少呢?這個要從51的啟動文件來分析,啟動文件中有這樣的匯編代碼:

?STACK SEGMENT IDATA ;定義一個片內(nèi)數(shù)據(jù)段,段名:?STACK

RSEG ?STACK ;選擇之前定義過的一個可重定位的段?STACK,下面的匯編語句將會被放置到該段,直到遇到下一個段定位指令,例如CSEG/RSEG。

DS 1 ;預(yù)留存儲區(qū)命令。聲明先占用一個字節(jié)的空間,在編譯時,這個預(yù)留的空間不會被其他變量所使用。在這里的意義是,給硬件棧分配1個byte(實際這樣是有問題的,應(yīng)該為硬件棧預(yù)留更多空間)

還有:

MOV SP,#?STACK-1

由上可見,SP被初始化為#?STACK-1,在#?STACK地址處,DS指令預(yù)留了N個字節(jié)的空間,這些空間就是硬件棧的空間

但啟動文件的代碼中,DS 1相當(dāng)于只給硬件棧預(yù)留了1個字節(jié),這實際上會出問題,原因如下:片內(nèi)RAM中會有多個數(shù)據(jù)段,只要使用XX SEGMENT IDATA指令即可在片內(nèi)RAM中聲明一個數(shù)據(jù)段XX,如果整個工程程序中,聲明了多個數(shù)據(jù)段,?STACK數(shù)據(jù)段就只是片內(nèi)RAM中眾多數(shù)據(jù)段中的一個,如果只給?STACK段預(yù)留1個字節(jié),而?STACK數(shù)據(jù)段后面又有別的數(shù)據(jù)段,那么我們的硬件棧就只有1個字節(jié)了,一旦發(fā)生中斷,CPU寄存器自動入棧立即導(dǎo)致棧溢出,溢出后踩了別的變量的內(nèi)存,程序基本崩潰;對于這個問題,keil是這樣處理的:keil在鏈接階段總是把?STACK數(shù)據(jù)段鏈接為片內(nèi)RAM中的最后一個數(shù)據(jù)段,即使我們只給他預(yù)留了1個字節(jié),那也不要緊,反正該段后面沒有別的變量占用,只要SP別超出0X7F(片內(nèi)RAM地址的上限)就行了。通過觀察.m51(map文件)我們發(fā)現(xiàn),keil確實是把?STACK數(shù)據(jù)段放到了片內(nèi)RAM的最后,下面是某個51工程生成的map文件摘抄:

* * * * * * * D A T A M E M O R Y * * * * * * *

REG 0000H 0008H ABSOLUTE "REG BANK 0"

DATA 0008H 0002H UNIT ?C?LIB_DATA

IDATA 000AH 000DH UNIT ?ID?UCOS_II

0017H 0009H *** GAP ***

BIT 0020H.0 0000H.1 UNIT ?BI?SERIAL

0020H.1 0000H.7 *** GAP ***

IDATA 0021H 0041H UNIT ?STACK ; 作者注:就是這一行!

* * * * * * * X D A T A M E M O R Y * * * * * * *

XDATA 0000H 080EH UNIT ?XD?SERIAL

XDATA 080EH 0804H UNIT ?XD?MAIN

XDATA 1012H 0490H UNIT ?XD?UCOS_II

XDATA 14A2H 005CH UNIT _XDATA_GROUP_

為避免系統(tǒng)棧不夠用,一個比較穩(wěn)妥的辦法就是,用匯編指令DS給?STACK數(shù)據(jù)段預(yù)留更多的空間,上面這個51工程中在另一個匯編文件中又給?STACK數(shù)據(jù)留出了40H個字節(jié),這樣總共就有41H個字節(jié)了。這樣做的好處是可以在編譯鏈接階段即可排查堆棧錯誤,舉個例子: 假設(shè)片內(nèi)RAM中的數(shù)據(jù)段有很多,以至于,除了?STACK數(shù)據(jù)段之外,片內(nèi)RAM只剩2個字節(jié)了,而?STACK數(shù)據(jù)段我們只默認(rèn)采用了啟動文件中的配置預(yù)留一個字節(jié),這樣編譯沒有任何問題,keil給編譯通過了,但是運行過程中系統(tǒng)棧只有2個字節(jié),肯定是分分鐘就發(fā)生棧溢出,然后崩潰;假設(shè)片內(nèi)RAM中的數(shù)據(jù)段有很多,以至于,除了?STACK數(shù)據(jù)段之外,片內(nèi)RAM只剩2個字節(jié)了,而如果我們給?STACK數(shù)據(jù)段用DS指令分配40H個字節(jié),這樣keil在編譯時就會發(fā)現(xiàn)51的片內(nèi)RAM不足而報錯,無法編譯,從而在編譯鏈接階段幫助我們發(fā)現(xiàn)堆棧問題。

繼續(xù)上面的問題,SP復(fù)位后的初值是多少,SP復(fù)位后等于0X07,但是立即就被啟動文件通過語句MOV SP,#?STACK-1給改掉了,所以在進(jìn)入main函數(shù)時SP的值是啟動文件修改后的值,也即#?STACK-1(注,很好理解,這里-1是滿增棧的特性),那么#?STACK的值又是多少呢?看上面的匯編語句?STACK SEGMENT IDATA,這一句聲明?STACK段為一個可重定位的段,也就是說,?STACK段的首地址(#?STACK)在編譯器進(jìn)行程序鏈接時才能確定下來,也就是說,#?STACK的值是在鏈接時由編譯器自動分配的,編譯階段不分配。仍然以上面摘抄的這段map文件為例,我們發(fā)現(xiàn),?STACK段的起始地址是0021H,也就是說,#?STACK就等于21H。

仿真棧是keil為51生成可重入函數(shù)時用的(通過給函數(shù)使用關(guān)鍵詞 REENTRANT限定,可使該函數(shù)具備可重入特性),對于STM32來說,默認(rèn)生成的函數(shù)(不含全局變量和靜態(tài)局部變量的函數(shù))就是可重入的,而keil為51生成的函數(shù),即使這個函數(shù)不含全局變量和靜態(tài)局部變量,默認(rèn)情況下keil也不會把這個函數(shù)匯編成可重入的,我認(rèn)為keil主要是考慮到51的片內(nèi)RAM匱乏,在不外接RAM的情況下,函數(shù)如果被編譯為可重入的,可重入函數(shù)的執(zhí)行需要占用一定的??臻g(尤其是由可重入函數(shù)嵌套調(diào)用產(chǎn)生的長的調(diào)用鏈,所需的棧更多)。

可重入函數(shù)在執(zhí)行過程中是需要使用棧的,那么51的可重入函數(shù)使用的棧在哪呢?是SP指向的那個系統(tǒng)棧嗎?答案是:不是。下面是解釋:

當(dāng)我們給51外擴(kuò)了大的片外RAM時,就不用擔(dān)心RAM不夠的問題了,但是還有一個問題,系統(tǒng)棧指針SP只能尋址0~7FH共128字節(jié)的空間,可重入函數(shù)肯定不允許被編譯成使用系統(tǒng)棧,否則,就算外擴(kuò)了RAM,這個外擴(kuò)RAM又無法供系統(tǒng)棧來使用,外擴(kuò)RAM就沒有意義了,所以keil為51打造了一個仿真棧的概念,keil在啟動文件中聲明了一個1或2字節(jié)的變量作為棧指針,這個棧指針的名字和大小根據(jù)編譯模式的不同而不同,以大編譯模式(注釋2)為例,大編譯模式下,啟動文件中的XBPSTACK常量需要程序員手動設(shè)置為1,這樣啟動文件中使用到的條件編譯,將會引用到一個2字節(jié)的仿真棧指針?C_XBP,由于keil把仿真棧作為滿減棧,所以這個仿真棧指針?C_XBP被初始化為片外RAM地址的最大值加1,若我們外接了一個64K的片外RAM,該RAM的最大地址是0XFFFF,那么棧指針?C_XBP被初始化為0XFFFF+1=溢出為0x0000。再舉一個小編譯模式的例子,小編譯模式是用來給沒有外擴(kuò)RAM的51用的,這樣51只能使用片內(nèi)0~127共128字節(jié)的RAM(這128RAN中還有一部分是Rn等,留給程序可用的RAM就更少了),在小編譯模式下,keil給51生成的仿真棧指針名叫?C_IBP,同時需要程序員手動把IBPSTACK常量設(shè)置為1,指針?C_IBP的初值被初始化為可用RAM的最大地址(127)加1,也即0x7f+1。關(guān)于小編譯模式small、壓縮編譯模式compact、大編譯模式large在堆棧處理上方面的不同,可參考這篇文章點擊打開鏈接,如果鏈接掛了,可自行搜索:《Keil模式設(shè)置和編程事項》。

注釋1:滿增棧,滿指的是SP總是指向最后一個入棧的字節(jié)的地址,增指的是每入棧一次,SP變大。相應(yīng)的,還有空增棧、空減棧、滿減棧,空指的是SP總是指向棧中下一個空閑位置的地址。

注釋2:如何選擇大編譯模式:以keil5為例,依次選擇->魔術(shù)棒->Target選項卡,Memory Model選擇Large:var...,Code Rom Size選擇Large....

附:舉一個不可重入函數(shù)使用中可能發(fā)生的陷阱,假設(shè)有分別有如下兩個函數(shù),第一個可重入,第二個不可重入

int add5_re(char a1,char a2,char a3,char a4,char a5) REENTRANT

{

int sum;

sum=a1+a2+a3+a4+a5;

return sum;

}

int add5(char a1,char a2,char a3,char a4,char a5)

{

int sum;

sum=a1+a2+a3+a4+a5;

return sum;

}

這兩個函數(shù)的形參以及局部變量分配等信息我們查閱.m51文件,分別如下(分號后面的注釋是博主自己加上的):

[plain] view plain copy------- PROC _?ADD5_RE

x:0002H SYMBOL a1 ;注意,地址標(biāo)號前為小x,指a1倍分配到了仿真棧中

x:0003H SYMBOL a2

x:0004H SYMBOL a3

x:0005H SYMBOL a4

x:0006H SYMBOL a5

------- DO

x:0000H SYMBOL sum

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

------- PROC _ADD5

D:0007H SYMBOL a1 ;R7

D:0005H SYMBOL a2 ;R5

D:0003H SYMBOL a3 ;R3

X:14ABH SYMBOL a4 ;注意地址標(biāo)號前為大X,指外部RAM

X:14ACH SYMBOL a5

------- DO

D:0006H SYMBOL sum ;R6

我們發(fā)現(xiàn),add5中的形參和局部變量a1/a2/a3/sum分到了Rn中,a4/A5分到了外部RAM xdata的絕對地址處,如果我們在main的調(diào)用鏈中和中斷函數(shù)中都調(diào)用了add5這個函數(shù),就會發(fā)生錯誤,假設(shè)恰好在main的調(diào)用鏈中執(zhí)行add5時發(fā)生了中斷,切換到中斷函數(shù)中去執(zhí)行add5,那么main調(diào)用鏈中的a1/a2/a3/sum因為被分到了Rn中,進(jìn)入中斷會切換register BANK,使得main調(diào)用鏈中的a1/a2/a3/sum沒有被破壞,得以幸免,但是a4/a5因為被分配到了絕對地址中,在中斷執(zhí)行完add5以后,main鏈條中的add5的a4/a5肯定會被破壞!!

對于可重入的add5_re函數(shù),即使main調(diào)用鏈和中斷同時調(diào)用它也不會出現(xiàn)上述被破壞的情形,因為add5_re的形參和局部變量全部都被定義到了仿真棧中(見上述代碼注釋),main調(diào)用鏈中使用add5_re函數(shù)會申請??臻g,中斷時add5_re又會申請新的棧空間。

還要注意的是,因為keil編譯51程序時,使用了覆蓋技術(shù)(不同函數(shù)的形參和局部變量可分時共享同一個絕對內(nèi)存單元),這也有可能產(chǎn)生陷阱,假設(shè)這樣一種情況:有一個函數(shù)func2( )的局部變量b在編譯后被分配到了絕對xdata的地址14ABH處,和上文的add5的a4變量共享內(nèi)存,這種情況下,即使 { func2( )僅在中斷中被調(diào)用,main調(diào)用鏈中不調(diào)用func2( )}、且{ add5僅在main調(diào)用鏈中被調(diào)用,中斷中不調(diào)用add5 },也會出問題,原因是顯而易見的,如果在add5執(zhí)行過程中發(fā)生中斷,中斷中使用過變量b之后,會破壞add5中的變量a4。究其原因在于,共享地址的編譯方式生成的函數(shù),只要分時調(diào)用就不會產(chǎn)生被破壞的情形,但是發(fā)生中斷導(dǎo)致了分時機制被破壞,以至于產(chǎn)生了同時調(diào)用。

結(jié)論:中斷中使用的函數(shù),要么是可重入的,要么是該函數(shù)的局部變量全部是獨享內(nèi)存單元的。

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險,如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學(xué)會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團(tuán))股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉