在前面的系列文章中,尤其是CM3/4之LR寄存器、EXC_RETURN深入分析 中我们简单的分析了一些函数调用与中断调用的流程,那篇文章也基本上能说明函数调用和中断调用的一些区别,本文将基于栈帧的概念,再次分析一下两者之间的相同点和不通点。
栈帧栈帧是一个非常专业的概念,很惭愧的说,在没有深入分析RTOS之前,我也不曾了解栈帧这一概念,之前只是大概了解到函数调用需要堆栈存放局部、临时变量,但是栈帧这个概念确实时从未关注过。我们先引入《The Definitive guide to ARM Cortex M3/4》中对栈帧的概念介绍:
翻译出来,有如下关键点:
- 栈帧是一块数据(the block of data)。
- 这块数据会压栈(pushed to the stack)。
- 在中断进入时进行压栈。
综上:起码在CM3中,栈帧特指发生中断时,CPU硬件自动保存现场的一块数据。,以CM3为例,栈帧内的数据包括:R0, R1, R2, R3, R12, R14(LR), R15(PC 返回地址), xPSR。
栈帧生成,自动压栈的流程如下:
答案是:为了用C程序编写中断服务函数。
这个答案似乎有些突兀,我按照我自己的理解来分析一下:
- C语言在嵌入式硬件广泛应用的时间并不长,也就20年左右,而单片机的历史已经非常长了,比如我们国内单片机教材中提到的MCS-51第一代其实是1980年的产物,C语言虽然是1972年发明的,但是发明之初并不是为了嵌入式单片机,直到1989年才有第一个C标准,也就是C89。笔者在零几年上大学的时候,教材里仍然是使用汇编语言编写单片机89C51程序。所以在没有使用C语言之前,应用程序,尤其是是中断服务程序都是使用汇编语言写的。
- 当C语言在嵌入式大行其道时,相比汇编语言,C语言对程序员简直是太友好了,所以理所当然的,程序员是希望中断服务函数也能像C函数一样方便编写。
如果用C语言编写一个中断服务程序,那么中断服务程序就是函数形式,那么问题就来了,函数和中断服务程序的运行机制是有区别的。我们先看下C语言函数运行机制的规则是如何定义的:
上面说的非常专业,但其实翻译过来,结合我们的使用习惯,有如下重点:
- R0~R3, R12, R14(LR), PSR 这7个寄存器称为“caller saved registers”,叫需要调用者进行保存的寄存器,这是因为函数调用返回后,这些寄存器可能还需要用,则调用者需要保存这7个寄存器, 比如R0-R3一般用于函数的入参,R0用于函数的返回值。
- R4~R11这8个寄存器称为“callee-saved registers”, 叫“被调用者保存寄存器”, 这是因为这些寄存器再函数内部,随时可能会被使用,如果不想在函数退出后,这些寄存器值影响,则需要被调用者去保存。
- 上述的解释还是有些绕,我们可以简单的这么理解:
R0~R3, R12, R14(LR), PSR这7个, 有需要,则需要在函数调用前就保存。
R4~R11值8个寄存器,根据需要,在函数内部进行保存。
综上,如果用C语言编写一个中断服务程序,那么这个中断服务程序也是一个C函数,既然是函数,就要符合C函数调用的规则,也就是符合AAPCS规则,但是中断毕竟是跟函数有些不通的,主要有以下不同点:
| – | 同步/异步 | 退出机制 |
|---|---|---|
| 函数调用 | 同步调用(明确知道何时调用) | 通过LR地址退出,调用进入前,CPU自动更新返回地址到LR |
| 中断 | 异步发生(不知道何时发生) | EXC_RETURN机制,进入中断服务前,CPU自动计算EXC_RETURN,赋值给LR,LR不是真实的返回地址 |
所以,目标就来了,既要让中断调用符合C函数的规范,又要满足中断机制,怎么办,ARM的解决方法是这样:
- 由于中断时随机发生的,而进入中断前可能还需要保存R0~R3,R12, R14, PSR,这部分如果让开发者来做肯定是没戏的,因为开发者不知道啥时候发生中断的,那只能是CPU自己来做了,要不然这事儿无解,所以发生中断时,在进入中断服务程序这个C函数前,需要硬件自动保存R0-R3, R12, R14, PSR这7个寄存器。
- 由于中断的退出与函数有区别,函数退出比较简单, CPU把退出地址更新到LR就可以了,函数退出的时候,再把LR给PC就可以了。但是中断不通,中断需要由EXC_RETURN机制,因为EXC_RETURN保存了发生中断前的CPU工作模式、MSP/PSP使用情况这些信息,本办法啊,这是OS需要的特性,而且偏偏EXC_RETURN还赋值给了LR,怎么办呢?可以新增一个专用寄存器,但是专用寄存器制造成本高啊,最后,得了,还是CPU自己把PC的值也保存起来吧,加上那7个,总共8个,组成一个栈帧吧。
终于,ARM相对完美解决了【既能用C编写中断服务程序,又能区别中断和函数,就能满足AAPCS规则】的问题。
小结- 栈帧是发生中断时,CPU硬件自动保存现场R0~R3, R12, R14, PC, PSR共8个(不同架构可能更多,比如浮点)寄存器的操作。
- 在发生中断时,CPU之所以硬件自动保存栈帧,是为了满足使用C能够直接编写中断服务程序,同时又能满足AAPCS的规范的平衡策略。
- 自动保存栈帧 + EXC_RETURN机制,完美解决了【既能用C编写中断服务程序,又能区别中断和函数,就能满足AAPCS规则】的问题。



