• 无论对于M3/M4还是A7,处理中断的第一步都是保存现场
  • 现场就是指程序运行时的那些寄存器,保存现场就是把寄存器保存到栈中;恢复现场就是处理完中断后把栈中的值恢复到寄存器中
  • CPU内部由R0-R15、PSR等通用寄存器,难道发生中断时要保存全部的寄存器吗?

    • 不需要,只需要保存用到了的寄存器
    • ARM架构中,程序调用有些规则(ATPCS规则),根据规则可以知道那些寄存器可能会被使用
    • 在C函数中,可以修改R0-R3、R12、R14(LR)以及PSR。
    • 如果C函数要用到这些寄存器,就要把它们保存到栈里,在函数结束前在从栈中恢复它们。
    • 这些寄存器被拆分成2部分:调用者保存的寄存器(R0-R3,R12,LR,PSR)被调用者保存的寄存器(R4-R11)

      比如函数A调用函数B,函数A应该知道:

      • R0-R3是用来传参数给函数B的
      • 函数B可以肆意修改R0-R3
      • 函数A不要指望函数B帮你保存R0-R3
      • 保存R0-R3,是函数A的事情
      • 对于LR、PSR也是同样的道理,保存它们是函数A的责任

      对于函数B:

      • 我用到R4-R11中的某一个,我都会在函数入口保存、在函数返回前恢复
      • 保证在B函数调用前后,函数A看到的R4-R11保存不变

      假设函数B就是异常/中断处理函数,函数B本身能保证R4-R11不变,那么保存现场时,只需要保存这些:

      • 调用者保存的寄存器(R0-R3,R12,LR,PSR)
      • PC
  • 实际上普通的函数调用中(不涉及模式切换、运行在用户模式时),调用者一般只是保存PC到LR中,然后再由被调用函数进行寄存器保存

    • R0-R3用作传参,所以被调用函数应该会使用,不用保存
    • R4-R11被用作被调用者的局部变量,所以被调用者需要将这些寄存器保存到栈中
    • R12(IP)、R13(SP)、R14(LR)、R15(PC)这些寄存器依情况而定
    • 还有CPSR状态寄存器也是需要保存的
  • 涉及模式切换一般都是异常发生了(用户模式到特权模式),异常发生是不可预测的,可能发生在任何时候
    • 在发生异常模式是,会去到异常向量表中调用对应的异常模式处理函数,异常模式处理函数中会判断具体是什么异常,进而调用具体的异常处理函数
    • 在异常模式处理函数中,需要进行保存现场(保存)
    • 这个保存现场需要保存的是R0-R3,R12,LR,PSR这些调用者需要保存的寄存器,而在具体的异常处理函数中再保存R4-R11这些被调用者要保存的寄存器
  • A7中, 除了sys模式和用户模式公用一套寄存器外,其他异常模式几乎都有自己的SP和LR寄存器
    • 不管发生了那种异常,硬件再切换到对应模式时都会将PC值保存到该模式的LR寄存器中(如:MOV LR_irq, PC)
    • 每种模式都有自己的SP栈指针,所以用户模式的SP指针并不需要保存
    • 异常处理的总入口函数中还会调用其他具体的处理函数,所以其模式独有的LR寄存器是要在保存现场的操作中进行保存的,因为再次调用其他函数时b/bl指令会更新LR寄存器的值

cortex-m3/m4

  • 硬件保存现场:image.png

  • image.png

  • 调用函数时一般LR=返回地址,这在一般的函数调用中当然没有问题;但是在中断中,如果只是设置LR为返回地址的话,CPU到时跳转到返回地址去执行,寄存器的值并没由恢复到原来值,这该怎么办呢?

    C函数执行完后,它返回LR所指示的位置。难道把LR设置为被中断的程序的地址就行了吗?如果只是返回LR所指示的地方,硬件帮我们保存在栈里的寄存器,怎么恢复?M3/M4在调用异常处理函数前,把LR设置为一个特殊的值,转给特殊的值被称为EXC_RETURN

  • M3/M4的CPU硬件设计了一个特殊值,当返回得知LR(实际是要赋给PC)是这个特殊值时,CPU就明白了要先恢复现场;真正的返回地址随着此前保存现场被保存到栈中了,所以恢复现场后就可以跳转到实际的返回地址上执行程序了

  • 当PC寄存器的值等于EXC_RETURN时,会触发异常返回机制,简单地说:会从栈里恢复R0-R3,R12,LR,PC,PSR等寄存器。
  • image.png
  • EXC_RETURN有6个取值,这些地址值并不对应任何内存、也不对应任何flash;正常函数返回地址不可能是这些值,当CPU发现值是这些时就知道了这是异常的返回
  • EXC_RETURN有写比较有意思的位,写程序时比较关心的是bit2(bit2表示用的是哪个栈,也就说保存现场时用的是sp_main还是sp_process)

    补充2个知识点:

    • 操作模式:M3/M4有两个操作模式
      • 处理模式:执行中断服务程序等异常处理时,处于处理模式
      • 线程模式:执行普通应用程序代码时,处于线程模式
    • M3/M4有连个SP寄存器:SP_process、SP_main
  • 有些RTOS在运行用户程序时会使用SP_process,默认使用SP_main。

cortex-a7

  • image.png
  • 对于A7寄存器,有多种操作模式,这些模式下都有其自身的SP寄存器;用户模式和系统模式共用一套寄存器
    • 几乎每个模式下都有自己是SP寄存器,意味着这些模式下有自己的栈
  • 这意味着在这些模式下,我们都必须设置它们各自的栈sp
  • image.png
  • 保存现场保存到的是对应异常模式下的栈(如: SP_irq)

    当发生异常时,以IRQ为例:

  • CPU会自动切换进入对应的模式,比如进入IRQ模式
  • 并且会把被中断是的CPSR保存到SPSR_irq里

所以发生异常/中断时,在保存现场时,只需要保存:

  • 调用者保存的寄存器(R0-R3,R12,LR)
  • PC