源代码:
cpp
__asm void xPortPendSVHandler( void )
{
extern uxCriticalNesting;
extern pxCurrentTCB;
extern vTaskSwitchContext;
/* *INDENT-OFF* */
PRESERVE8
mrs r0, psp ; 获取进程栈指针(当前任务的栈)
isb ; 指令同步屏障
/* Get the location of the current TCB. */
ldr r3, =pxCurrentTCB ; 获取当前任务控制块指针
ldr r2, [ r3 ] ; r2 = pxCurrentTCB
/* Is the task using the FPU context? If so, push high vfp registers. */
tst r14, #0x10 ; 检查EXC_RETURN bit 4
it eq ; 如果bit4=0,说明使用了FPU
vstmdbeq r0!, {s16-s31} ; 保存FPU高寄存器s16-s31
/* Save the core registers. */
stmdb r0!, {r4-r11, r14} ; 保存CPU寄存器 r4-r11, r14(EXC_RETURN)
/* Save the new top of stack into the first member of the TCB. */
str r0, [ r2 ] ; 更新TCB中的栈顶指针
/* 以下为执行任务切换 */
stmdb sp!, {r0, r3} ; 临时保存 r0, r3
mov r0, # configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r0 ; 提升中断优先级(屏蔽某些中断)
dsb ; 数据同步屏障
isb ; 指令同步屏障
bl vTaskSwitchContext ; 调用C函数选择下一个任务
mov r0, # 0
msr basepri, r0 ; 恢复中断屏蔽
ldmia sp!, {r0, r3} ; 恢复 r0, r3
/* The first item in pxCurrentTCB is the task top of stack. */
ldr r1, [ r3 ] ; r1 = 新的pxCurrentTCB
ldr r0, [ r1 ] ; r0 = 新任务的栈顶指针
/* Pop the core registers. */
ldmia r0!, {r4-r11, r14} ; 恢复CPU寄存器
/* Is the task using the FPU context? If so, pop the high vfp registers
* too. */
tst r14, # 0x10 ; 检查是否需要恢复FPU上下文
it eq
vldmiaeq r0!, {s16-s31} ; 恢复FPU高寄存器
msr psp, r0 ; 更新进程栈指针
isb
#ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata */
#if WORKAROUND_PMU_CM001 == 1
push { r14 }
pop { pc }
nop
#endif
#endif
bx r14 ; 异常返回
/* *INDENT-ON* */
}
解析:
上下文切换核心:切换前保存所有相关内核寄存器,切换后恢复所有相关内核寄存器;这里的所有相关内核寄存器就是指:R0-R12,SP,LR,PC,PSR以及有FPU的则还包括FPU相关寄存器;
其中R0-R3,R12,SP,LR,PC,PSR已经在中断执行的时候由硬件机制入栈了,所以只需要将R4-R11,R14手动入栈就行了,这里之所以要再将R14入栈一次,是因为硬件机制入栈的R14是指LR,已经处于中断中时R14是表示EXCRETURN,代表的意义不一样;
所以,以上代码的流程总结就是:
1.保存内核寄存器
2.更新新的栈顶指针(因为有入栈)
3.切换下一个任务(实际上就是将pxCurrentTCB 指向新任务)
4.获取新任务的栈顶指针
5.恢复内核寄存器
6.异常返回(执行之后硬件会自动POP R0-R3等寄存器)
注意:上诉代码能有效的还一个重要信息就是: TCB的第一个字保存的是该任务栈的栈顶地址