- 任务切换的本质是CPU寄存器中值的切换,现在要由任务A切换至B,就要将目前CPU中寄存器的值(A任务的值)压栈入A的任务中去,堆栈再将任务B的值(在B的任务堆栈中)出栈到CPU的寄存器中。
- 只有在异常或特殊中断时才使用MSP,FreeRTOS中任务A的自动压栈和任务B的自动出栈都在中断之外进行,也就是使用PSP指针。
- 任务切换的过程在PendSV中断服务函数中进行。
- 触发PendSV中断服务函数的本质上是通过向中断控制和状态寄存器(ICSR)的第28位写入1来挂起PendSV。
- 当使能时间片调度时,当有同等优先级的多个任务时,每一个滴答定时器中断周期就调用一次PendSV中断服务函数,还可以通过API函数来触发PendSV中断服务函数,用在很多函数里。
- 压栈时,前一部分寄存器的值硬件会自动保存,后一部分需要我们手动压栈。
出栈时,需要先手动出栈,在自动出栈。 - mrs r0, psp
自动压栈后PSP指针会指向R0这个寄存器的地址,( mrs r0, psp)将PSP任务栈指针赋值给r0,也就是说现在r0->PSP->R0 - ldr r3, =pxCurrentTCB
ldr r2, [ r3 ]
r3获取当前运行的任务的TCB的地址。
r2获取r3这个地址下的值 ,也就是这个地址下第一个元素的地址,也就是栈顶指针的地址 - stmdb r0 !, { r4 - r11 }
str r0, [ r2 ]
从r0这个地址开始压栈, r4 - r11这些寄存器,最后r0指向任务堆栈最低的一个地址,也就是栈顶地址
str r0, [ r2 ] ,之前r2获取了栈顶指针,把r0这个地址写入r2的内存里边,现在栈顶指针里边的值指向任务栈最低地址。
也就是说当需要回复现场时,只要有任务控制块,就能找到栈顶指针,而栈顶指针里边的值就是任务栈最低地址。 - stmdb sp !, { r3, r14 }
将 r3, r14压栈,保存到MSP里边,r3保存的是A的TCB - mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r0
关中断 - bl vTaskSwitchContext
mov r0, #0
msr basepri, r0
找到下一个要运行的任务,开中断 - ldmia sp !, { r3, r14 }
出 r3, r14的栈 - ldr r1, [ r3 ]
ldr r0, [ r1 ] /* The first item in pxCurrentTCB is the task top of stack. */
r1获取 r3这个地址下的值,获取TCB的地址
r0获取 r1这个地址下的值,The first item in pxCurrentTCB,也就是栈顶指针,得到栈顶指针就能得到栈顶指针里边的值(栈底的地址),r0现在指向栈底地址 - ldmia r0 !, { r4 - r11 } /* Pop the registers and the critical nesting count. */
msr psp, r0
从这个r0地址回复寄存器的值
将 r0赋值给psp - bx r14
执行任务B