日本高清不卡中文字幕-一起草草视频在线观看-亚洲精品一区二区三区色-国产亚洲精品免费视频

你好!歡迎來(lái)到深圳市穎特新科技有限公司!
語(yǔ)言
當(dāng)前位置:首頁(yè) >> 技術(shù)中心 >> 單片機(jī)入門(mén) >> 51單片機(jī)的仿真棧(模擬棧/可重入棧)

51單片機(jī)的仿真棧(模擬棧/可重入棧)

關(guān)鍵字:單片機(jī) 堆棧 作者:admin 來(lái)源:不詳 發(fā)布時(shí)間:2018-05-18  瀏覽:13

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

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

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

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

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

還有:

MOV SP,#?STACK-1

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

但啟動(dòng)文件的代碼中,DS 1相當(dāng)于只給硬件棧預(yù)留了1個(gè)字節(jié),這實(shí)際上會(huì)出問(wèn)題,原因如下:片內(nèi)RAM中會(huì)有多個(gè)數(shù)據(jù)段,只要使用XX SEGMENT IDATA指令即可在片內(nèi)RAM中聲明一個(gè)數(shù)據(jù)段XX,如果整個(gè)工程程序中,聲明了多個(gè)數(shù)據(jù)段,?STACK數(shù)據(jù)段就只是片內(nèi)RAM中眾多數(shù)據(jù)段中的一個(gè),如果只給?STACK段預(yù)留1個(gè)字節(jié),而?STACK數(shù)據(jù)段后面又有別的數(shù)據(jù)段,那么我們的硬件棧就只有1個(gè)字節(jié)了,一旦發(fā)生中斷,CPU寄存器自動(dòng)入棧立即導(dǎo)致棧溢出,溢出后踩了別的變量的內(nèi)存,程序基本崩潰;對(duì)于這個(gè)問(wèn)題,keil是這樣處理的:keil在鏈接階段總是把?STACK數(shù)據(jù)段鏈接為片內(nèi)RAM中的最后一個(gè)數(shù)據(jù)段,即使我們只給他預(yù)留了1個(gè)字節(jié),那也不要緊,反正該段后面沒(méi)有別的變量占用,只要SP別超出0X7F(片內(nèi)RAM地址的上限)就行了。通過(guò)觀(guān)察.m51(map文件)我們發(fā)現(xiàn),keil確實(shí)是把?STACK數(shù)據(jù)段放到了片內(nèi)RAM的最后,下面是某個(gè)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)棧不夠用,一個(gè)比較穩(wěn)妥的辦法就是,用匯編指令DS給?STACK數(shù)據(jù)段預(yù)留更多的空間,上面這個(gè)51工程中在另一個(gè)匯編文件中又給?STACK數(shù)據(jù)留出了40H個(gè)字節(jié),這樣總共就有41H個(gè)字節(jié)了。這樣做的好處是可以在編譯鏈接階段即可排查堆棧錯(cuò)誤,舉個(gè)例子: 假設(shè)片內(nèi)RAM中的數(shù)據(jù)段有很多,以至于,除了?STACK數(shù)據(jù)段之外,片內(nèi)RAM只剩2個(gè)字節(jié)了,而?STACK數(shù)據(jù)段我們只默認(rèn)采用了啟動(dòng)文件中的配置預(yù)留一個(gè)字節(jié),這樣編譯沒(méi)有任何問(wèn)題,keil給編譯通過(guò)了,但是運(yùn)行過(guò)程中系統(tǒng)棧只有2個(gè)字節(jié),肯定是分分鐘就發(fā)生棧溢出,然后崩潰;假設(shè)片內(nèi)RAM中的數(shù)據(jù)段有很多,以至于,除了?STACK數(shù)據(jù)段之外,片內(nèi)RAM只剩2個(gè)字節(jié)了,而如果我們給?STACK數(shù)據(jù)段用DS指令分配40H個(gè)字節(jié),這樣keil在編譯時(shí)就會(huì)發(fā)現(xiàn)51的片內(nèi)RAM不足而報(bào)錯(cuò),無(wú)法編譯,從而在編譯鏈接階段幫助我們發(fā)現(xiàn)堆棧問(wèn)題。

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

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

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

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

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

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

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

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;

}

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

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

x:0002H SYMBOL a1 ;注意,地址標(biāo)號(hà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)號(hà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的絕對(duì)地址處,如果我們?cè)趍ain的調(diào)用鏈中和中斷函數(shù)中都調(diào)用了add5這個(gè)函數(shù),就會(huì)發(fā)生錯(cuò)誤,假設(shè)恰好在main的調(diào)用鏈中執(zhí)行add5時(shí)發(fā)生了中斷,切換到中斷函數(shù)中去執(zhí)行add5,那么main調(diào)用鏈中的a1/a2/a3/sum因?yàn)楸环值搅薘n中,進(jìn)入中斷會(huì)切換register BANK,使得main調(diào)用鏈中的a1/a2/a3/sum沒(méi)有被破壞,得以幸免,但是a4/a5因?yàn)楸环峙涞搅私^對(duì)地址中,在中斷執(zhí)行完add5以后,main鏈條中的add5的a4/a5肯定會(huì)被破壞!!

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

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

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

編輯:admin  最后修改時(shí)間:2018-05-18

聯(lián)系方式

0755-82591179

傳真:0755-82591176

郵箱:vicky@yingtexin.net

地址:深圳市龍華區(qū)民治街道民治大道973萬(wàn)眾潤(rùn)豐創(chuàng)業(yè)園A棟2樓A08

Copyright © 2014-2023 穎特新科技有限公司 All Rights Reserved.  粵ICP備14043402號(hào)-4

额尔古纳市| 阳江市| 商南县| 张家界市| 华宁县| 类乌齐县| 赤水市| 怀化市| 安达市| 乌兰察布市| 金溪县| 蓬安县| 阳新县| 广西| 资中县| 曲沃县| 广安市| 康保县| 巴青县| 涿鹿县| 进贤县| 基隆市| 邹城市| 牙克石市| 古丈县| 西宁市| 三江| 宣武区| 庆云县| 崇仁县| 龙井市| 乾安县| 房山区| 广南县| 平原县| 武义县| 历史| 九龙县| 莱芜市| 威宁| 德保县|