知识点:
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