保存现场几种场景

场景一:普通函数调用 (如 main调用fun)

  • 触发机制:由BL(Branch with Link)等跳转指令在软件层面主动触发

  • 谁来完成编译器自动生成的代码来完成。

  • 保存了哪些寄存器

    • R4-R11:如果被调用的函数(fun)内部使用了这些寄存器,它会在函数的开头(入口处)用PUSH指令将它们压栈,在返回前用POP恢复。

    • LRBL指令执行时,CPU硬件会自动将返回地址存入LR。但若fun内部还有嵌套调用,它也会把LR压栈保存。

  • 原理:这确保了函数调用前后,调用者(main)的R4-R11内容保持不变,保证了程序的确定性。


场景二:异常/硬件中断 (如外部中断、SysTick)

  • 触发机制:由外部事件或内部外设在硬件层面异步触发

  • 谁来完成CPU硬件自动完成一部分,程序员/编译器完成另一部分。

  • 保存了哪些寄存器:分两步进行。

    1. 硬件自动压栈 (8个寄存器):当异常发生时,Cortex-M4内核会自动将以下8个寄存器压入当前使用的栈中(PSP或MSP),组成一个“栈帧”。

      • R0-R3,R12,LR,PC,xPSR

      • 这在硬件上保证了中断处理函数可以作为普通的C函数来编写。

    2. 软件可选保存:如果中断服务程序(ISR)需要用到R4-R11,编译器会像普通函数一样,在ISR的入口处生成代码来保存它们。

  • 特殊点中断嵌套与返回。处理异常时,LR寄存器会被赋一个特殊值EXC_RETURN,用于指示异常返回时使用哪个栈指针(MSP或PSP)以及返回到线程模式还是处理模式,从而支持嵌套中断。


场景三:FreeRTOS任务切换

  • 触发机制:由SysTick时钟滴答或任务主动阻塞,在软件层面主动触发PendSV(可挂起服务调用)异常来执行。

  • 谁来完成CPU硬件自动完成一部分,FreeRTOS的移植层代码 (port.c)完成其余部分。

  • 保存了哪些寄存器:这是保存最完整、最彻底的一种场景。

    1. 硬件自动压栈:触发PendSV异常后,硬件首先自动保存了和场景二相同的R0-R3,R12,LR,PC,xPSR到当前任务的栈中。

    2. 软件强制保存所有剩余寄存器:紧接着,在PendSV的中断服务函数(通常是xPortPendSVHandler)中,FreeRTOS的汇编代码会主动将当前任务的所有其他通用寄存器,包括R4-R11全部压栈保存。

  • 原理:因为任务切换是完全独立的上下文切换,当前任务的执行现场(所有寄存器的值)必须被完整地保存到它自己的任务栈中,等到下一次它获得CPU时才能精确地恢复现场,继续执行。

总结