中断函数、函数调用 --- 出栈入栈原理分析
子函数或者中断函数的最后,一般的语句都是BX LR 返回调用点,但是两者的含义略有不同
中断函数
当系统发生中断/异常的时候,CM3 处理器会:
1、入栈:将 8 个寄存器的值压入栈,其中 LR 的值是中断前的值;
2、取向量:从向量表中获取对应中断的 ISR 入口地址;
3、取出MSP,更新到栈指针 SP 中,根据进入中断前的状态更新链接寄存器 LR为EXC_RETURN,更新 PC为中断函数;
入栈就是在进入中断/异常服务程序之前的现场保存,硬件自动将 xPSR、PC、LR、R12、R3、R2、R1、R0 压入栈,如果当中断/异常发生时刻,正在使用 PSP,则压入 PSP;否则压入 MSP;一旦进入 ISR,那就一直使用 MSP;
R0~R3 和 R12 自动入栈了,那其他的 R4~R11 呢, 依据 ARM 的 C 语言标准函数调用约定(AAPCS),如果真的用到了 R4~R11,C语言编译为汇编时,编译器生成代码来入栈它们,因为AAPCS是约束C语言到汇编,如果中断函数是用汇编写的,则需要自己编写汇编代码入栈;
当中断最后调用BX LR把EXC_RETURN送给 PC,CPU检测到向PC中载入的是这个特殊值时,就知道是中断返回,于是做中断返回的动作(与压入动作相反:从堆栈中弹出核心寄存器的值,恢复到线程模式或Handler模式等)
ExceptionEntry()伪代码,描述的就是进中断函数前的操作
子函数
BL调用子函数,LR保存的是调用函数后的下一个指令地址。
调用子函数,编译器知道在哪一行代码中进入子函数,所以C语言编译为汇编代码后,在子函数使用的寄存器,如果父函数也使用了,在父函数调用前压栈,子函数返回前会出栈。所以进入子函数前,是否压栈,压栈什么寄存器,是由编译器决定的,编译器依据AAPCS标准。
中断是随时随刻都可能触发,编译器无法提前判断,所以进入中断前的入栈,和退出中断前的出栈,也是依据AAPCS,不过入栈出栈是硬件自动实现。
子函数最后执行BX LR,相当于MOV LR, PC(PC = LR)
参考资料
《Armv7-M Architecture Reference Manual》