上一篇文章中,為了提取jlink-ob的bin文件,需要到j(luò)linkARM.dll中獲取,因此必須要了解STM32編譯后的指令特點(diǎn)及其存儲(chǔ)特性,所以這一篇先來(lái)看看堆棧和存儲(chǔ),以及SP這個(gè)棧頂?shù)刂肥窃趺创_定的。
如何獲取Jlink-ob的固件
今天來(lái)科普一下堆棧的概念,以及SMT32存儲(chǔ)器是如何存儲(chǔ)代碼的。
堆和棧的概念其實(shí)是漸進(jìn)式的來(lái)看,首先堆棧是一種數(shù)據(jù)結(jié)構(gòu),其次程序運(yùn)行的時(shí)候利用了這樣的數(shù)據(jù)結(jié)構(gòu),在MCU的內(nèi)存中營(yíng)造出了這兩個(gè)區(qū)域來(lái)配合程序執(zhí)行。
一,什么是堆棧和隊(duì)列
堆棧對(duì)應(yīng)到英文單詞就是heap和stack。
這里要注意,在數(shù)據(jù)結(jié)構(gòu)中,隊(duì)列和的堆棧是不同類(lèi)型的數(shù)據(jù)結(jié)構(gòu)。
我記得初學(xué)計(jì)算機(jī)的時(shí)候,有一個(gè)很有意思的題目--摞盤(pán)子。
我現(xiàn)在每天的生活中都在親歷這個(gè)題目,晚飯后,我會(huì)把廚房收拾干凈,所有的盤(pán)子都洗干凈摞在一起。
旁邊那一摞盤(pán)子其實(shí)就是我設(shè)計(jì)的一個(gè)棧,他有一個(gè)特點(diǎn),那就是我做飯的時(shí)候一定先要從最上面拿起一個(gè)盤(pán)子乘菜。而當(dāng)我刷鍋洗碗時(shí),我會(huì)把洗好的盤(pán)子再放回最上面。如果覺(jué)察到下面的盤(pán)子積灰了,那說(shuō)明我們的棧深度還足夠,否則我會(huì)認(rèn)為最近做的菜有點(diǎn)多了。好了,上面就是一個(gè)數(shù)據(jù)結(jié)構(gòu) — 棧的問(wèn)題,下面我們看一下雞蛋的問(wèn)題。額,不是雞蛋,是隊(duì)列的問(wèn)題。
我最近買(mǎi)了一個(gè)儲(chǔ)存雞蛋的盒子,設(shè)計(jì)的很巧妙,買(mǎi)來(lái)雞蛋我就從上面一個(gè)一個(gè)的放進(jìn)去,然后放在冰箱的最上面。需要雞蛋的時(shí)候,我就從下面取一顆,源源不斷。你會(huì)發(fā)現(xiàn),我吃的永遠(yuǎn)是剩的最陳舊的那一顆雞蛋,這就是隊(duì)列。
二,STM32 的FLASH與RAM的存儲(chǔ)
到了計(jì)算機(jī)系統(tǒng)中,堆和棧被分別了開(kāi)來(lái)。其中棧保持原有的特性,也可以叫做堆棧,它是一種運(yùn)算受限的線(xiàn)性表,限制僅允許在表的一段進(jìn)行插入和刪除操作,可以被操作的這一端叫做棧頂,另一端叫做棧底,也就是容易積灰的那一端。棧在程序運(yùn)行的過(guò)程中主要負(fù)責(zé)存儲(chǔ)局部變量,比如我們函數(shù)中定義的變量,如果控制器內(nèi)核的寄存器不夠用了,它就會(huì)把這個(gè)變量放到棧里面先存一會(huì)。在操作系統(tǒng)中,任務(wù)切換時(shí)的現(xiàn)場(chǎng)參數(shù)保存也是存放到對(duì)應(yīng)任務(wù)的棧(這里的棧其實(shí)放在STM32的全局變量里面)里面。上面有點(diǎn)迷糊,這里我們主要討論STM32裸機(jī)程序的堆棧情況。在STM32中,堆是用來(lái)給分配動(dòng)態(tài)內(nèi)存的,系統(tǒng)只有用到malloc的時(shí)候才會(huì)使用這個(gè)區(qū)間,后面在講這個(gè)位置。我先看看,每次編譯完程序,編譯器鏈接后,會(huì)提供一個(gè)生成信息。
代碼編譯后提示信息可以看到的內(nèi)容
- Code :是代碼占用的空間,存儲(chǔ)到Flash【ROM】中的程序代碼。RO-data:是 Read Only 只讀常量的大小,如const修飾的變量。用來(lái)存儲(chǔ)程序的指令和常量,保存在Flash【ROM】中。RW-data:是(Read Write) RW是可讀可寫(xiě)變量,就是初始化時(shí)候就已經(jīng)賦值了的(上電前就已經(jīng)確定值的),RW + ZI就是你的程序總共使用的RAM字節(jié)數(shù)。ZI-data:是(Zero Initialize) 沒(méi)有初始化的可讀寫(xiě)變量的大小,就是程序中用到的變量并且被系統(tǒng)初始化為0的變量的字節(jié)數(shù)。
Total?ROM?Size?(Code?+?RO?Data?+?RW?Data)這樣所寫(xiě)的程序占用的ROM的字節(jié)總數(shù),這部分在我們進(jìn)行程序下載的時(shí)候,會(huì)全部存儲(chǔ)在SMT32的Flash中。
當(dāng)系統(tǒng)上電后,內(nèi)核會(huì)將一部分?jǐn)?shù)據(jù)存搬運(yùn)到RAM當(dāng)中,因?yàn)槲覀兌x的很多變量是可讀取的,所以要放到RAM中。這其中包括RW-data和ZI-data。對(duì)于RW-data,需要一點(diǎn)一點(diǎn)的搬運(yùn)過(guò)去,既然是搬運(yùn),那么在FLASH中本來(lái)就會(huì)存儲(chǔ)一份,所以RW-data會(huì)占用Flash空間。而對(duì)于ZI-data,由于它沒(méi)有初始化,所以它的初始值無(wú)所謂,所以我們只需要知道它的個(gè)數(shù),在RAM中直接畫(huà)一片地方給他用就行了,因此ZI-data無(wú)需單獨(dú)在Flash中存儲(chǔ)一份。我們所說(shuō)的堆棧,就在上述執(zhí)行過(guò)程中同樣的被畫(huà)了一塊自留地。
三,SMT32的堆棧自留地
那么在STM32的RAM中,堆棧是如何量取確定的呢?我們先看一張圖:
上圖中指示了STM32的RAM區(qū)域存儲(chǔ)結(jié)構(gòu),下面是低地址,上面是高地址。編譯器在編譯和鏈接后,是可以計(jì)算出全局變量,局部變量以及一些未初始化的全局變量所占用空間的,我們把這個(gè)總和計(jì)算出來(lái),現(xiàn)在RAM上畫(huà)一片地方來(lái)存放,也就是上圖的靜態(tài)存儲(chǔ)區(qū)域。然后我們?cè)佼?huà)一片自留地給到定義好的HEAP區(qū)域,最后畫(huà)一片給到STACK,也就是我們說(shuō)的棧。我們?cè)诖a中看一下如何定義的。我手頭有一個(gè)CW32的例程,用這個(gè)來(lái)示意一下。在項(xiàng)目中的startupxx.s文件中,用匯編定義了堆和棧的大小。
如果我們不使用malloc進(jìn)行動(dòng)態(tài)分配內(nèi)存,那么這里的Heap_Size完全可以定義為0,不過(guò)編譯器直接把這個(gè)事給干了,也就是說(shuō),如果它發(fā)現(xiàn)你沒(méi)有使用malloc,編譯器會(huì)直接移除這部分空間,可以在例程編譯生成的map文件中看到。
所以,沒(méi)必要手賤的去改這個(gè)匯編文件。再說(shuō)RAM空間的堆疊方式,它是先放的靜態(tài)變量,然后放的堆空間(編譯器會(huì)優(yōu)化掉),最后放的棧空間,所以只要我們改變棧空間大小,那么棧頂指針就會(huì)變化。實(shí)驗(yàn)一下吧。
接下來(lái),我們把棧改大一倍,編譯看看,棧頂?shù)刂肥遣皇窍鄳?yīng)的變大了
是不是?童叟無(wú)欺!那么,如果我們?cè)黾屿o態(tài)存儲(chǔ)區(qū)的大小,這個(gè)棧頂?shù)刂窇?yīng)該也會(huì)改變。如何增加靜態(tài)存儲(chǔ)區(qū)大小呢?定義一個(gè)全局變量就好了。記得把編譯器的優(yōu)化等級(jí)去掉哦,不然編譯器會(huì)偷偷把你沒(méi)用的變量啥的都給你移除掉,實(shí)驗(yàn)就沒(méi)效果了。
我注釋掉了一個(gè)4字節(jié)的數(shù)組,棧的大小也改回了0x200,編譯后,我的棧頂?shù)刂纷兂闪?x20000e60。接下來(lái),我把注釋打開(kāi),增加一個(gè)4字節(jié)的數(shù)組。
我們可以看到,棧頂?shù)刂反_實(shí)增加了,從原來(lái)的0x20000e60增加到了0x20000e68。為什么地址一下增加了8呢?于是我有改小了一下數(shù)組,重新編譯后,棧頂?shù)刂酚肿兓亓?x20000e60。
有興趣的朋友研究下,咱們?cè)u(píng)論區(qū)聊聊。