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一般是满减栈,freertos中,有portSTACK_GROWTH宏控制,portSTACK_GROWTH 小于0为向下增长,大于0向上增长
- 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)
- );
- }
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中记录了中断返回的地址,跳转执行