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