在嵌入式開發(fā)中,堆棧是一個很基礎(chǔ),同時也是非常重要的名詞,堆??煞譃槎?(Heap) 和棧 (Stack) 。
- 棧(Stack): 一種順序數(shù)據(jù)結(jié)構(gòu),滿足后進先出(Last-In / First-Out)的原則,由編譯器自動分配和釋放。
- 堆(Heap):類似于鏈表結(jié)構(gòu),可對任意位置進行操作,通常由程序員手動分配,使用完需及時釋放(free),不然容易造成內(nèi)存泄漏。
1、棧
SP:stack pointer 棧指針,總是指向棧頂。
計算機中的堆棧主要用來保存臨時數(shù)據(jù)、局部變量、存寄存器參數(shù)和中斷/調(diào)用子程序程序的返回地址。
裸機中,SP 指向在系統(tǒng)啟動文件中被設(shè)置為一個被預留大小的內(nèi)存塊頂部,每次調(diào)用函數(shù),把需要的臨時變化放入棧中,函數(shù)退出后,恢復為調(diào)用之前的值。
棧的作用:
- 保存現(xiàn)場
- 傳遞參數(shù):匯編代碼調(diào)用C函數(shù)時,需傳遞參數(shù)
- 保存臨時變量:包括函數(shù)的非靜態(tài)局部變量以及編譯器自動生成的其他臨時變量
2、棧操作
Cortex-M 中堆棧方向是向低地址方向增長,為滿堆棧機制。棧一般放在 .bss 段之后
C語言會自動入棧出棧,所以程序員不需要關(guān)心這些(在匯編的時候加入)。匯編語言需要手工處理入棧出棧。
3、Cortex-M中的棧
在 ARM Cortex-M 中 SP 是通用寄存器,為 R13 寄存器
在 Corte-M 中采用雙棧設(shè)計,分為 MSP 和 PSP。
MSP 和 PSP 的含義是 Main_Stack_Pointer 和 Process_Stack_Pointer,在邏輯地址上他們都是 R13。
權(quán)威手冊上說的很清楚 PSP 主要是在 Handler 的模式下使用,MSP 主要在線程模式下使用(當然你在線程模式下也可以調(diào)用PSP,需要你做特殊的處理)
這意味著同一個邏輯地址,實際上有兩個物理寄存器,一個為 MSP,一個為 PSP,在不同的工作模式調(diào)用不同的物理寄存器。在任何一個時刻只能使用一個堆棧指針,要么使用 MSP,要么使用 PSP。
-
MSP:主堆棧指針,當程序復位后(開始運行后),一直到第一次任務(wù)切換完成前,使用的都是 MSP,即:
main()
函數(shù)運行時用的是 MSP。 -
PSP:進程堆棧指針,切換任務(wù)之后 PendSV 服務(wù)程序中有
ORR LR, LR, #0x04
這句,意思就是 PendSV 中斷返回后使用的 PSP 指針,此時 PSP 已經(jīng)指向了所運行任務(wù)的堆棧,所以返回后就可以就接著該任務(wù)繼續(xù)運行下去了。
裸機中只會用到 MSP,當 main()
函數(shù)開始運行前,啟動文件會給這個函數(shù)分配一個堆棧空間,用于保存 main()
函數(shù)運行過程中變量的保存。此時MSP就指向了該堆棧的首地址。
4、MDK中的SP操作流程
以 STM32F103C8T6 為例分析在 MDK 中 SP 相關(guān)的運行流程。其中 STM32F103C8T6 內(nèi)存為 20K(0x5000),地址:0x20000000 ~ 0x20005000。
STM32 中的啟動文件 startup_stm32f10x_md.s 文件與 SP 相關(guān)部分代碼:
; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
PRESERVE8
THUMB
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
; External Interrupts
DCD WWDG_IRQHandler ; Window Watchdog
DCD PVD_IRQHandler ; PVD through EXTI Line detect
DCD TAMPER_IRQHandler ; Tamper
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line 0
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7
DCD ADC1_2_IRQHandler ; ADC1_2
DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX
DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; EXTI Line 9..5
DCD TIM1_BRK_IRQHandler ; TIM1 Break
DCD TIM1_UP_IRQHandler ; TIM1 Update
DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM4_IRQHandler ; TIM4
DCD I2C1_EV_IRQHandler ; I2C1 Event
DCD I2C1_ER_IRQHandler ; I2C1 Error
DCD I2C2_EV_IRQHandler ; I2C2 Event
DCD I2C2_ER_IRQHandler ; I2C2 Error
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI Line
DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
AREA |.text|, CODE, READONLY
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
__user_initial_stackheap
LDR R0, = Heap_Mem
LDR R1, =(Stack_Mem + USR_Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR
- __initial_sp:指向棧頂,在運行后會賦值給 MSP。Stack_Size:棧大小,當前分配為 0x400。
- __heap_base:堆開始地址;__heap_limit:堆結(jié)束地址;Heap_Size:堆大小,當前分配為 0x200。
- __Vectors:中斷向量表入口地址,__Vectors_End:中斷向量表結(jié)束地址;__Vectors_Size:中斷向量表大小。
Cortex-M 采用矢量中斷模式,中斷向量表首地址放的是棧頂?shù)刂罚╛_initial_sp)。
- 堆/棧初始化:導出相關(guān)變量。MDK 中,是否使用 Micro-LIB,對棧地址影響很大,下面重點講一下。
5、Micro-Lib的SP差別
1. 使用 Micro-Lib
使用 EXPORT 偽指令分別導出 __initial_sp
、__heap_base
、__heap_limit
,在 __main
中會處理完后跳轉(zhuǎn)到 C 語言的 main()
函數(shù)。
- 查看 MAP 文件可以得到相關(guān)的地址信息:
__initial_sp 0x20000408 Data 0 startup_stm32f10x_md.o(STACK)
Execution Region RW_IRAM1 (Exec base: 0x20000000, Load base: 0x08001b78, Size: 0x00000408, Max: 0x00005000, ABSOLUTE)
Exec Addr Load Addr Size Type Attr Idx E Section Name Object
0x20000000 0x08001b78 0x00000004 Data RW 212 .data main_gc9a01.o
0x20000004 0x08001b7c 0x00000004 Data RW 3332 .data mc_w.l(errno.o)
0x20000008 - 0x00000400 Zero RW 186 STACK startup_stm32f10x_md.o
注:查看上面的 MAP 文件,在使用 Micro-LIB 模式下,heap 其實是沒有被分配的。
- 通過 SWD 連接芯片,查看 SP 地址
在 startup_stm32f10x_md.s 中 Reset_Handler 中第一句話,SP=0x20000408;進入 main 之后,SP=0x200003F0;進入子函數(shù)后:SP=0x200003E8。MSP 與 SP 地址一樣。
- 在 main() 中通過代碼打印獲取以上變量
extern uint32_t __Vectors_End;
extern uint32_t __Vectors;
extern uint32_t __Vectors_Size;
printf("__Vectors: %08x\r\n", (uint32_t)&__Vectors);
printf("__Vectors_End: %08x\r\n", (uint32_t)&__Vectors_End);
printf("__Vectors_Size: %08x\r\n", (uint32_t)&__Vectors_Size);
extern uint32_t __initial_sp;
printf("__initial_sp: %08x\r\n", (uint32_t)&__initial_sp);
運行結(jié)果:
__Vectors: 0x08000000
__Vectors_End: 0x080000EC
__Vectors_Size: 0x000000EC # 59 * 4 = 0xec
__initial_sp: 0x20000408
__Vectors 的值與 __initial_sp 的值一致。
2. 未使用 Micro-Lib
-
使用 IMPORT 偽指令導入
__use_two_region_memory
,該函數(shù)需要用戶實現(xiàn)。 -
使用 EXPORT 偽指令導出
__user_initial_stackheap
,該函數(shù) startup_stm32f10x_md.s 中已經(jīng)實現(xiàn),用于提供編譯器的初始化C庫函數(shù)設(shè)置用戶程序的堆棧所需要的堆棧信息。文章來源:http://www.zghlxwxcb.cn/news/detail-691581.html
LDR R0, = Heap_Mem ;堆頂
LDR R1, =(Stack_Mem + Stack_Size) ;棧頂
LDR R2, = (Heap_Mem + Heap_Size) ;堆末地址
LDR R3, = Stack_Mem ;棧首地址
BX LR ;等同于mov pc, lr,跳轉(zhuǎn)并切換指令集,也就是切換到ARM指令集
- 查看 MAP 文件可以得到相關(guān)的地址信息:
Execution Region RW_IRAM1 (Exec base: 0x20000000, Load base: 0x08002330, Size: 0x00000668, Max: 0x00005000, ABSOLUTE)
Exec Addr Load Addr Size Type Attr Idx E Section Name Object
0x20000000 0x08002330 0x00000004 Data RW 212 .data main_gc9a01.o
0x20000004 - 0x00000060 Zero RW 3383 .bss c_w.l(libspace.o)
0x20000064 0x08002334 0x00000004 PAD
0x20000068 - 0x00000200 Zero RW 187 HEAP startup_stm32f10x_md.o
0x20000268 - 0x00000400 Zero RW 186 STACK startup_stm32f10x_md.o
- 通過 SWD 連接芯片,查看 SP 地址
在 startup_stm32f10x_md.s 中 Reset_Handler 中第一句話,SP=0x20000668;進入 main 之后,SP=0x20000650;進入子函數(shù)后:SP=00x20000648文章來源地址http://www.zghlxwxcb.cn/news/detail-691581.html
- __Vectors 的值與棧頂?shù)刂芬恢?br>
到了這里,關(guān)于ARM Cortex-M 的 SP的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!