大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是ARM Cortex-M堆栈机制。
今天给大家分享的这篇依旧是2016年之前痞子衡写的技术文档,花了点时间重新编排了一下格式。前面痞子衡讲过 《嵌入式里的堆栈原理》,本篇算是堆栈原理的工程实践,更具体点说是在ARM Cortex-M上的应用。ARM Cortex-M家族发展至今已经很多代,我们且以最简单的Cortex-M0为例来讲述堆栈机制:
1.基本规则
1.1 R13 / sp寄存器
R0-R12为通用寄存器,R13为系统堆栈指针sp,堆栈指针是用于访问堆栈,也即系统的RAM区。Cortex-M0中采用了两个堆栈指针:主堆栈指针(MSP)和进程堆栈指针(PSP),R13在任何时刻只能是其中一个,默认情况为MSP,可以通过控制寄存器(CONTORL)来改变。
MSP是系统复位后(即其处于Handler Mode)的指定sp(vector table的前4Byte自动载入),用于处理异常中断。当结束Reset_Handler后,cpu进入正常运行状态(即其处于Thread Mode),仅在此状态下PSP才能被使用,当然MSP也可以使用。其后如有硬中断来临,则进入Handler Mode,如果硬件中断结束,则返回Thread Mode。
关于MSP和PSP的选用,其是通过CONTORL寄存器来配置,仅在Thread Mode下才可设置CONTORL寄存器。一般情况下,没有必要使用PSP,除非是有os存在时,MSP用于os内核的sp,而PSP用于thread级app的sp,这两个sp需严格分开。
在编译器中,可以通过r13(R13)或sp(SP)来访问堆栈(具体是MSP和PSP由当时环境决定);也可以通过指定的MRS、MSR指令来访问MSP和PSP。
1.2栈结构
无OS的堆栈结构:
有OS的堆栈结构:
1.3栈操作
Cortex-M0中堆栈方向是向低地址方向增长,为满堆栈机制。堆栈操作是通过PUSH和POP来完成操作的。
栈一般放在ARM的 RAM高位区,如某MCU中RAM地址为0x20000000-0x20007fff,共32KByte。栈大小设为4KByte的话,其地址一般就放在0x20007000-0x20007fff,其中0x20007000为绝对栈顶,0x20007ffc为绝对栈底,sp总是指向相对栈顶。第一个PUSH数据被存在绝对栈底(此时绝对栈底也是相对栈顶)。实际上,除了POP指令可以从栈顶中取数据外;MOV指令也可从任意位置取数据,但不会影响栈结构(即不影响其sp)。
由于ARM寄存器均是32bit,故PUSH和POP指令均是32bit访问,故sp指针总是至少4Byte对齐(低2bit永远为0)。有时编译器也会分配8Byte对齐的栈,这是由于double浮点类型需要占用8Byte,为了处理方便,故将栈设为8Byte对齐。
2.入栈顺序
入栈顺序因编译器、处理器系统、OS而异,C语言中并没有强制规定入栈顺序,此处主要是讲ARM Cortex-M系列处理器在指定编译器情况下的入栈顺序。
2.1一般函数调用(通用)
上图展示了在一般函数(无参无局部变量无返回值)嵌套调用时,关于sp的操作。在执行BL FunctionA指令时,LR记录的是BL FunctionA的下一条顺序指令,在进入FunctionA后执行的第一条操作便是PUSH {LR}即将下一条顺序指令压入栈中,然后才开始执行FunctionA函数体。函数体执行结束之后,使用POP {PC}指令将栈顶数据弹到PC中,即可返回继续执行BL FunctionA的下一条顺序指令。
2.2极端函数调用(平台而异)
考虑一种极端情况来详细讲述入栈顺序,即函数含有4个参数以上,函数体内定义了多个局部变量,并且还有返回值。这个情况比较特殊,痞子衡专门在IAR上做过一次实验,详见下图:
至此,ARM Cortex-M堆栈机制痞子衡便介绍完毕了,掌声在哪里~~~
欢迎订阅
文章会同时发布到我的 博客园主页、CSDN主页、微信公众号 平台上。
微信搜索"痞子衡嵌入式"或者扫描下面二维码,就可以在手机上第一时间看了哦。