ARM
Contents
异常与中断处理流程
程序执行方式
处理流程
保存与恢复现场
为什么要保存现场
需要保存的现
场(寄存器)
M3/4硬件保存与
恢复现场流程
1 异常与中断处理流程
1.1 程序执行方式
ARM中控制程序执行的三种方式:
- 正常程序是顺序执行的,在CM3中每执行一条指令,PC += 4;
- 通过跳转指令,程序可以跳转到特定的地址标号/特定的子程序处执行;
- 当异常/中断发生时,系统执行完当前指令后,将跳转到相应的ESR/ISR处执行。当执行完后程序返回发生中断的指令的下一条指令处执行。(进入ESR/ISR前先保护被中断程序的执行现场,从异常程序退出时,要恢复现场)
1.2 处理流程
1.2.1 Cortex-M3/4处理流程
参考资料:DDI0403E_B_armv7m_arm.pdf 、ARM Cortex-M3与Cortex-M4权威指南.pdf 、PM0056.pdf
- 保存现场(硬件):把被中断瞬间的寄存器的值保存进栈里
- 分辨异常/中断(硬件)
- 根据异常/中断号,从向量表中得到函数地址,跳转过去执行
- 函数执行完后,从栈中恢复现场(软件触发,硬件恢复)
1.2.2 Cortex-A7处理流程
参考资料:ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf
- CPU切换到对应的异常模式
- 用户模式(usr)
- 快速中断模式(fiq)
- 外部中断模式(irq)
- 特权模式(sve)
- 中止模式(abort)
- 未定义指令模式(undefined)
- 系统模式(sys)
- 保存被中断时的CPSR到SPSR
- CPSR:current program status register,当前程序状态寄存器
- SRSR:saved program status register,保存的程序状态寄存器
- 向量表上放置的是跳转指令(如发生任何中断时,CPU会从向量表里找到下面第6项(0~7),得到
ldr pc, _irq 指令,执行后就跳转到_irq函数),在跳转到的函数中:
_start:
b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
上面启动代码来自:u-boot-arch-arm-lib-vectors.s
综上,两者区别:
- 保存现场:cortex M3/M4里是硬件完成,cortex A7等是软件实现
- 分辨异常/中断:cortex M3/M4里是硬件完成,cortex A7等是软件实现
- 调用处理函数:cortex M3/M4里是硬件来调用,cortex A7等是软件自己去调用
- 恢复现场:cortex M3/M4里是软件触发、硬件实现,cortex A7等是软件实现
2 保存与恢复现场
2.1 为什么要保存现场
举例分析:
上述4条汇编指令,它们被异常打断时,需要保证在异常处理完后,被打断的程序还能正确运行,需要保存的数据:
-
执行完第1条指令时程序被打断,恢复运行时,R0要保持不变 -
执行完第2条指令时程序被打断,恢复运行时,R0、R1要保持不变 -
执行完第3条指令时程序被打断,比较结果保存在程序状态寄存器里,恢复运行时,程序状态寄存器保持不变
上述指令要读取a、b内存,当被打断时无需保存,因为只要程序不越界,内存就保持不变。因此关键在于R0、R1、程序状态寄存器的保存:
- 在处理异常前,把这些寄存器保存在栈中,即为保存现场
- 在处理完异常后,从栈中恢复这些寄存器,即为恢复现场
2.2 需要保存的现场(寄存器)
根据ATPCS:C函数可以修改R0~R3、R12、R14(LR)及PSR寄存器(M3/M4中称为XPSR ,A7中称为CPSR )。若C函数需要使用R4~R11,就应该把它们保存到栈里,在函数结束前在从栈中恢复它们。
因此,寄存器被拆分成2部分:
- 调用者保存的寄存器(R0-R3,R12,LR,PSR):函数调用子程序后,若还需使用其中某些寄存器,则在调用前需保存到栈中,调用结束后再从栈中恢复。
- 被调用者保存的寄存器(R4-R11):被调用者需要使用其中某些寄存器时,则需将它们保存到栈中,函数退出前恢复它们的原始值,因此其他程序可能在使用这些寄存器。
例如,函数A(调用者)调用函数B(被调用者)时:
对于函数A:
- R0-R3用来传参数给函数B,函数B可能会修改这些寄存器,函数A若还需使用,则必须保存它们;
- R12,LR,PSR若还需使用,也要保存
对于函数B:
- 用到R4-R11中的某些寄存器,都要在函数入口保存、在函数返回前恢复
- 保证在B函数调用前后,函数A看到的R4-R11保持不变
函数B就可以视为异常/中断处理函数,在函数A被打断,执行异常/中断处理前,需要保存:
- 保存调用者使用到的寄存器(R0-R3,R12,LR,PSR)
- PC
2.3 M3/4硬件保存与恢复现场流程
2.3.1 异常堆栈帧(exception stack frame)
在异常入口处被压入栈空间的数据块为栈帧。对于Cortex-M3或不具有浮点单元的Cortex-M4处理器,栈帧都是8 个字(32字节)大小的,对于具有浮点单元的Cortex-M4则可能是8或26个字。
ATPCS要求栈指针的数值在函数入口和出口处是双字对齐的,即SP的值必须能被8整除,若在中断产生时栈帧未对齐到双字上,M3/4会自动插入一个字(栈本身即是字<4字节>对齐的),双字对齐特性可通过SCB->CCR 的STKALIGN位设置,若异常不符合ATPCS,则可将该特性关闭。(F1默认不开启,但启动文件使用PRESERVE8 做了8字节对齐)也可以通过伪指令REQUIRE8 和PRESERVE8 配置堆栈8字节对齐。
进入异常前,依次把xPSR, PC, LR, R12以及R3-R0由硬件自动压入适当的堆栈中(响应异常时,若当前代码正在使用PSP,则压入PSP,否则就压入MSP),其中,PC(SP-0x24 处)即为图中返回地址:
▲ 双字栈对齐时,Cortex-M3或Cortex-M4(无浮点单元)处理器的异常栈帧
压栈时xPSR的bit9表示栈指针SP数值是否被调整过,上图为双字对齐,因此不会额外插入一个字,且xpsr bit9 = 0 。若使能了双字对齐特性,且SP数值未对齐到双字边界上,栈中会被插入一段空间,SP被强制对齐到双字地址,且xpsr bit9 = 1 ,表明插入了一段区域:
▲ 非双字栈对齐时,Cortex-M3或Cortex-M4(无浮点单元)处理器的异常栈帧
在自动入栈的过程中,把寄存器写入堆栈内存的时间顺序,并不是与写入的空间顺序相对应的。但是机器会保证:正确的寄存器将被保存到正确的位置:
▲ 内部入栈序列
- 先保存PC与xPSR:可以更早地启动服务例程指令的预取——因为这需要修改PC;同时,也做到了在早期就可以更新xPSR中
IPSR位段 的值。 - 后压入R3-R0,R12:为了更容易地使用SP基址来索引寻址(这也方便了LDM等多重加载指令。因为LDM必须加载地址连续的一串数据,而现在R0-R3, R12的存储地址连续了),这种顺序也舒展了参数的传递过程,即可方便地通过读取入栈的R0-R3取出参数
入栈操作由数据(系统)总线完成,而指令总线同时会从向量表中找出正确的异常向量,然后在服务程序的入口处预取指,即入栈和取指同时进行。
2.3.2 更新寄存器
在入栈和取向量操作完成之后,执行服务例程之前,还要更新一系列的寄存器:
- SP:在入栈后会把堆栈指针(PSP或MSP)更新到新的位置。在执行服务例程时,将由MSP负责对堆栈的访问。
- PSR:更新IPSR位段的值为新响应的异常编号。
- PC:在取向量完成后,PC将指向服务例程的入口地址
- LR:LR =
EXC_RETURN
此外,NVIC中也会更新若干个相关有寄存器。
2.3.3 异常返回EXC_RETURN
M3/M4在调用异常处理函数前,把LR设置为一个特殊的值——EXC_RETURN ,当异常处理结束后,硬件将PC=EXC_RETURN ,触发异常返回机制:
- 恢复先前压入栈中的寄存器,堆栈指针的值也改回先前的值。
- 更新NVIC寄存器:活动位被硬件清除,对于外部中断,倘若中断输入再次被置为有效,悬起位也将再次置位。
EXC_RETURN 合法值如下:
▲ EXC_RETURN的位域
▲ EXC_RETURN的合法值
如果主程序在线程模式下运行,并且在使用MSP时被中断,则在服务例程中LR=0xFFFF_FFF9;若使用PSP时被中断,则在服务例程中LR=0xFFFF_FFFD(主程序被打断前的LR已被自动保存至异常栈帧):
如果主程序在Handler模式(MSP)下运行,则在服务例程中LR=0xFFFF_FFF1,在嵌套时,更深层ISR所看到的LR总是0xFFFF_FFF1:
执行异常返回:
- 可通过
EXC_RETURN bit2 判断异常返回的栈帧使用的是MSP还是PSP - 在每次出栈操作结束时,处理器还会检查出栈xPSR数值的第9位,并且若压栈时插入了额外的空间则会将其去除。
END
|