FreeRTOS 任务切换细节

知识点:

MSP:主堆栈指针,系统复位后,默认使用MSP指针,MSP指针用于操作内核以及处理异常和中断(异常是中断的一种,中断服务程序默认强制使用MSP指针,这是硬件自动设置的)

    不使用OS,非中断函数和中断函数都使用MSP

PSP:进程堆栈指针,任务(进程)使用PSP指针,在vPortSVCHandler中断服务函数中,通过修改 R14 的值从MSP指针切换到PSP指针

    使用OS,main函数和中断使用MSP,各个task使用PSP

R13: 堆栈指针SP,分为MSP和PSP

在执行异常的时候,SP以MSP为栈指针,在执行任务时SP以PSP为栈指针

每个任务都有一个任务堆栈

栈的地址是从高往低生长,堆栈指针最低两位永远是0,意味着堆栈总是4字节对齐

任务TCB数据结构第一个成员一定是指向任务当前堆栈栈顶的指针变量pxTopOfStack

R14作用:连接寄存器。调用子函数时存储返回地址;异常(中断)发生时,R14中保存异常返回标志,包括返回后进入线程模式还是处理器模式、指定SP使用PSP堆栈指针还是MSP堆栈指针。当调用 BX r14 指令后,硬件会知道要从异常返回,然后出栈,这个时候堆栈指针PSP已经指向了新任务堆栈的正确位置,当新任务的运行地址被出栈到PC寄存器后,新的任务也会被执行。

PC(R15):程序计数器,记录下一个要执行指令的地址,改变它的值就能改变程序的执行流

R0:任务形参

通用寄存器:用于数据操作

切换流程:

1、进入PendSV中断前,硬件自动将xPSR、PC(R15)、LR(R14)、R12、R0~R3压入任务堆栈中(使用PSP堆栈指针)

2、手动将R4~R11压入堆栈,将PSP栈顶值存入TCB(任务控制块)

3、关高优先级中断

4、调用任务切换函数获得新的任务控制块

5、开中断

6、通过新的任务控制块更新PSP栈顶值,手动将R4~R11出栈

7、退出中断时,硬件自动将xPSR、PC、LR、R12、R0~R3任务堆栈中出栈(使用PSP堆栈指针)

8、根据新的寄存器值和PSP执行新的任务

 

xPortPendSVHandler:
    mrs r0, psp
    isb
    ldr    r3, =pxCurrentTCB            /* Get the location of the current TCB. */
    ldr    r2, [r3]

    stmdb r0!, {r4-r11}                /* Save the remaining registers. */
    str r0, [r2]                    /* Save the new top of stack into the first member of the TCB. */

    stmdb sp!, {r3, r14}

  /* 接下来两句是进入临界区,即屏蔽高优先级中断 */
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY msr basepri, r0 dsb isb bl vTaskSwitchContext
  /* 接下来两句是退出临界区,即恢复高优先级中断 */
mov r0, #0 msr basepri, r0 ldmia sp!, {r3, r14} ldr r1, [r3] ldr r0, [r1] /* The first item in pxCurrentTCB is the task top of stack. */ ldmia r0!, {r4-r11} /* Pop the registers. */ msr psp, r0 isb bx r14

isb:清流水线,保证此操作完成后再执行下一条指令

ldr  r3, =pxCurrentTCB:把 pxCurrentTCB 的地址加载到 r3,相当于两个指针变量的赋值操作,此后 [r3] 相当于 pxCurrentTCB 的值,给 pxCurrentTCB 赋值相当于给 r3 赋值

ldr  r2,  [r3]:把r3指向的内容赋值给r2,相当于两个变量的赋值操作

stmdb  r0!,  {r4-r11}:先将 r0 指向的地址递减,再以新 r0 将 CPU 的 R4-R11 手动入栈,此时 R0 具体指向如下图:

str r0, [r2]:将R0的值存储到R2指向的内容,因为R2等于pxCurrentTCB,而pxCurrentTCB的第一个成员为pxTopOfStack,所以就是将R0存储到上一个任务的pxTopOfStack任务栈顶指针中。完成了上文的保存

stmdb  sp!,  {r3,  r14}:将 R3 和 R14 入栈,此刻的 SP 指向 MSP。对这两个寄存器入栈的原因:此刻的 R3 指向了 pxCurrentTCB(R3 保存了 pxCurrentTCB 的地址),在接下来的函数调用可能被修改,而这个值在函数调用后还要用到;R14 值在函数调用后同样要用到,而函数调用会修改 R14 的值(存储调用子函数后的返回地址)

ldr  r1,  [r3]: 调用函数 vTaskSwitchContext 后,pxCurrentTCB指向的内容就是新的TCB,进而 R3 指向的内容也是新的TCB

参考链接:

https://blog.csdn.net/qingzhuyuxian/article/details/80604129

https://blog.csdn.net/qq_15100379/article/details/86166994

原文地址:https://www.cnblogs.com/god-of-death/p/14855296.html