1 前言
当CM3开始响应一个中断时,会在它小小的体内奔涌起三股暗流:
入栈: 把8个寄存器的值压入栈;
取向量:从向量表中找出对应的服务程序入口地址;
选择堆栈指针MSP/PSP,更新堆栈指针SP,更新LR,更新程序计数器PC;
---引自《Cortex-M3权威指南》
为了提高异常响应的实时性,Cortex-M7采用了如下机制:
① 支持基于优先级的中断抢占
即便CPU当前正在进行异常处理,当新到异常的优先级较高时,即可进行抢占,转而处理该优先级较高的异常,即支持中断嵌套处理;
② 异常返回
如果当前无尚pending等待响应的异常,且没有被晚到(late-arriving)机制打断,当前异常处理完成后,就会进行异常返回;
③ 尾链机制(咬尾中断,tail-chaining)
为了加速异常响应速度,当一个异常处理完成后,如果还有处于pending状态的异常等待响应,则不会进行堆栈恢复,而是转而处理该pending的异常;
④ 晚到机制(late-arriving)
该机制主要是为了加速优先级抢占;若当前异常(A)响应正处于现场保护(压栈,保护现场等操作异常处理准备阶段),而新到一个优先级更高的异常B,则之前的准备工作都变成了为他人做嫁衣了,当然这些准备工作也不会浪费(这些准备工作也是新到的异常B处理所必须的),CPU会转而处理该优先级较高的异常,进行其对应的中断向量取指工作,待到异常B处理完成后,尾链机制生效,继续处理异常A;
当然,这种半路截胡的事情只限于异常处理准备阶段,一旦异常A进入异常处理(开始执行ISR),则只能按照普通的抢占处理,这就意味着需要进行当前异常处理的现场保护,需要更多的CPU耗时和压栈处理。
总的来看,整个异常处理过程可大概理解为图1的流程:
图1 异常处理过程
2 异常响应的具体行为分析
2.1 异常响应
一个异常能够得到CPU响应的情况,主要分为以下两种:
① 异常来临时,CPU处于线程模式(Thread mode),抢的毫无悬念;
②异常来临时,CPU正在处理异常,但新到异常的优先级更高,照抢不误;
当然,以前描述的情况都是在异常没有被屏蔽的情况下讨论的,PRIMASK, FAULTMASK以及BASEPRI是三个可以用于异常使能或除能的特殊功能寄存器。
2.2 压栈
如果当前异常不是以晚到机制或尾链机制进行处理的,那么在毕竟要亲自动手进行异常处理前的准备活动,包括保护现场,对诸多寄存器进行压栈(依次把xPSR, PC, LR, R12以及R3-R0由硬件自动压入适当的堆栈中),这8个寄存器由硬件(强制)自动完成压栈,其集合称之为栈帧(stack frame)。当Cortex-M7选配的FPU使能时,同时也需要对FPU相关寄存器进行压栈处理,具体如图2所示。
图2 stack frame
其中:
① 栈帧中PC存储的是中断返回后执行的地址,为被中断打断的程序的下一条指令地址;
② 压栈等准备工作完成后,若没有被更高优先级的中断抢占,则正式进入中断处理阶段,该中断的状态会从pending变为active,开始执行ISR;反之,若新到一个更高优先级的异常,则该中断的状态会停留在pending状态,CPU会转而处理该新到异常;
此外,在进入ISR前,xPSR, PC, LR, R12以及R3-R0由硬件自动压入适当的堆栈(MSP或PSP)中,而其他寄存器如果由保护需求,则需要在ISR中手动入栈。例如,PortPendSVHandler代码中r4-r11,r14及FPU相关寄存器的相关入栈操作就是在ISR中完成的:
void xPortPendSVHandler( void )
{
/* This is a naked function. */
__asm volatile
(
" mrs r0, psp \n"
" isb \n"
" \n"
" ldr r3, pxCurrentTCBConst \n"/* Get the location of the current TCB. */
" ldr r2, [r3] \n"
" \n"
" tst r14, #0x10 \n"/* Is the task using the FPU context? If so, push high vfp registers. */
" it eq \n"
" vstmdbeq r0!, {s16-s31} \n"
" \n"
" stmdb r0!, {r4-r11, r14} \n"/* Save the core registers. */
" str r0, [r2] \n"/* Save the new top of stack into the first member of the TCB. */
" \n"
" stmdb sp!, {r0, r3} \n"
" mov r0, %0 \n"
" cpsid i \n"/* Errata workaround. */
" msr basepri, r0 \n"
" dsb \n"
" isb \n"
" cpsie i \n"/* Errata workaround. */
" bl vTaskSwitchContext \n"
" mov r0, #0 \n"
" msr basepri, r0 \n"
" ldmia sp!, {r0, r3} \n"
" \n"
" ldr r1, [r3] \n"/* The first item in pxCurrentTCB is the task top of stack. */
" ldr r0, [r1] \n"
" \n"
" ldmia r0!, {r4-r11, r14} \n"/* Pop the core registers. */
" \n"
" tst r14, #0x10 \n"/* Is the task using the FPU context? If so, pop the high vfp registers too. */
" it eq \n"
" vldmiaeq r0!, {s16-s31} \n"
" \n"
" msr psp, r0 \n"
" isb \n"
" \n"
#ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata workaround. */
#if WORKAROUND_PMU_CM001 == 1
" push { r14 } \n"
" pop { pc } \n"
#endif
#endif
" \n"
" bx r14 \n"
" \n"
" .align 4 \n"
"pxCurrentTCBConst: .word pxCurrentTCB \n"
::"i" ( configMAX_SYSCALL_INTERRUPT_PRIORITY )
);
}
2.3 异常返回
在异常处理模式(Handler mode)下,将EXC_RETURN加载至PC,则会触发异常返回,通常有以下三种方式:
• An LDM or POP instruction that loads the PC.
• An LDR instruction with PC as the destination.
• A BX instruction using any register.
在ISR执行前,EXC_RETURN的值会被存储到LR,用于指示中断返回后使用的堆栈指针(MSP还是PSP)及处理器模式(线程模式还是异常处理模式),一旦EXC_RETURN被加载到PC,则标志着ISR执行完毕,随即触发中断返回序列。EXC_RETURN的bits[31:5]恒为1,而低5位用于存储返回信息,具体见表1:
表1 EXC_RETURN定义的异常返回行为