一、背景知识:
1、任务切换包含三个基本流程:保护现场、更新TCB、恢复现场并跳转
2、freertos的任务切换是在xPortPendSVHandler 中断函数中完成的
3、中断函数在调用之前,硬件已经保存了r0,r1,r2,r3,r12,r14(LR),r15(pc),恢复现场的时候由硬件自动恢复r0,r1,r2,r3,r12,r14(LR),r15(pc),即r0~r3在中断服务函数中是可以随便使用的,剩下的r4~r11需要在中断函数中手动保存。
4、ARM中的栈有两个,msp和psp,主程序和中断中用msp,线程中是psp
5、ARM一般是满减栈
二、xPortPendSVHandler源代码:
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)
);
}
三、xPortPendSVHandler分析:
1、先在xPortPendSVHandler中把pxCurrentTCB打印出来:
2、
将psp给r0
3、
这两行,将r3保存了pxCurrentTCB,r2指向了上一个task的栈顶位置:pxTopOfStack
4、
这是对FPU寄存器的判断,可以先不看
5、
将r4-r11,r14保存到任务栈psp中。并将栈顶位置写入到r2中,即 pxTopOfStack这个变量
6、
此时的sp为msp,即将r3、r0保存到msp中。
至此,现场保护已经完成。
7、
跳转到vTaskSwitchContext这个C函数中执行,这个函数中就将下一个要执行的任务的TCP更新到pxCurrentTCB中,主要代码如下:
这里要时刻注意,不要迷糊,更新pxCurrentTCB的同时,r3也会指向这个新的pxCurrentTCB。
8、
将r3和r0从msp中恢复出来,注意:此时的r3中的pxCurrentTCB已经是更新之后的了。
9、
这两句,将新的pxCurrentTCB中的pxTopOfStack给了r0,即r0中记录了新task的栈顶位置
10、
从新task中恢复现场即恢复r4~r11,r14
11、
恢复FPU相关寄存器
12、
将栈顶的位置给psp
至此,恢复现场已经全部完成
13、
r14即LR中记录了中断返回的地址,跳转执行