今天給大家分享的這篇依舊是 2016 年之前痞子衡寫的技術(shù)文檔,花了點(diǎn)時(shí)間重新編排了一下格式。前面痞子衡講過 《嵌入式里的堆棧原理》,本篇算是堆棧原理的工程實(shí)踐,更具體點(diǎn)說是在 ARM Cortex-M 上的應(yīng)用。ARM Cortex-M 家族發(fā)展至今已經(jīng)很多代,我們且以最簡單的 Cortex-M0 為例來講述堆棧機(jī)制:
1. 基本規(guī)則
1.1 R13 / sp 寄存器
R0-R12 為通用寄存器,R13 為系統(tǒng)堆棧指針 sp,堆棧指針是用于訪問堆棧,也即系統(tǒng)的 RAM 區(qū)。Cortex-M0 中采用了兩個(gè)堆棧指針:主堆棧指針(MSP)和進(jìn)程堆棧指針(PSP),R13 在任何時(shí)刻只能是其中一個(gè),默認(rèn)情況為 MSP,可以通過控制寄存器(CONTORL)來改變。
MSP 是系統(tǒng)復(fù)位后(即其處于 Handler Mode)的指定 sp(vector table 的前 4Byte 自動(dòng)載入),用于處理異常中斷。當(dāng)結(jié)束 Reset_Handler 后,cpu 進(jìn)入正常運(yùn)行狀態(tài)(即其處于 Thread Mode),僅在此狀態(tài)下 PSP 才能被使用,當(dāng)然 MSP 也可以使用。其后如有硬中斷來臨,則進(jìn)入 Handler Mode,如果硬件中斷結(jié)束,則返回 Thread Mode。
關(guān)于 MSP 和 PSP 的選用,其是通過 CONTORL 寄存器來配置,僅在 Thread Mode 下才可設(shè)置 CONTORL 寄存器。一般情況下,沒有必要使用 PSP,除非是有 os 存在時(shí),MSP 用于 os 內(nèi)核的 sp,而 PSP 用于 thread 級 app 的 sp,這兩個(gè) sp 需嚴(yán)格分開。
在編譯器中,可以通過 r13(R13)或 sp(SP)來訪問堆棧(具體是 MSP 和 PSP 由當(dāng)時(shí)環(huán)境決定);也可以通過指定的 MRS、MSR 指令來訪問 MSP 和 PSP。
1.2 棧結(jié)構(gòu)
無 OS 的堆棧結(jié)構(gòu):
有 OS 的堆棧結(jié)構(gòu):
1.3 棧操作
Cortex-M0 中堆棧方向是向低地址方向增長,為滿堆棧機(jī)制。堆棧操作是通過 PUSH 和 POP 來完成操作的。
棧一般放在 ARM 的 RAM 高位區(qū),如某 MCU 中 RAM 地址為 0x20000000-0x20007fff,共 32KByte。棧大小設(shè)為 4KByte 的話,其地址一般就放在 0x20007000-0x20007fff,其中 0x20007000 為絕對棧頂,0x20007ffc 為絕對棧底,sp 總是指向相對棧頂。第一個(gè) PUSH 數(shù)據(jù)被存在絕對棧底(此時(shí)絕對棧底也是相對棧頂)。實(shí)際上,除了 POP 指令可以從棧頂中取數(shù)據(jù)外;MOV 指令也可從任意位置取數(shù)據(jù),但不會(huì)影響棧結(jié)構(gòu)(即不影響其 sp)。
由于 ARM 寄存器均是 32bit,故 PUSH 和 POP 指令均是 32bit 訪問,故 sp 指針總是至少 4Byte 對齊(低 2bit 永遠(yuǎn)為 0)。有時(shí)編譯器也會(huì)分配 8Byte 對齊的棧,這是由于 double 浮點(diǎn)類型需要占用 8Byte,為了處理方便,故將棧設(shè)為 8Byte 對齊。
2. 入棧順序
入棧順序因編譯器、處理器系統(tǒng)、OS 而異,C 語言中并沒有強(qiáng)制規(guī)定入棧順序,此處主要是講 ARM Cortex-M 系列處理器在指定編譯器情況下的入棧順序。
2.1 一般函數(shù)調(diào)用(通用)
上圖展示了在一般函數(shù)(無參無局部變量無返回值)嵌套調(diào)用時(shí),關(guān)于 sp 的操作。在執(zhí)行 BL FunctionA 指令時(shí),LR 記錄的是 BL FunctionA 的下一條順序指令,在進(jìn)入 FunctionA 后執(zhí)行的第一條操作便是 PUSH {LR}即將下一條順序指令壓入棧中,然后才開始執(zhí)行 FunctionA 函數(shù)體。函數(shù)體執(zhí)行結(jié)束之后,使用 POP {PC}指令將棧頂數(shù)據(jù)彈到 PC 中,即可返回繼續(xù)執(zhí)行 BL FunctionA 的下一條順序指令。
2.2 極端函數(shù)調(diào)用(平臺而異)
考慮一種極端情況來詳細(xì)講述入棧順序,即函數(shù)含有 4 個(gè)參數(shù)以上,函數(shù)體內(nèi)定義了多個(gè)局部變量,并且還有返回值。這個(gè)情況比較特殊,痞子衡專門在 IAR 上做過一次實(shí)驗(yàn),詳見今天次條推文(是個(gè)長圖,看懂需要有一定匯編基礎(chǔ)):