问题:
在RTOS内核中,任务切换的原理是:手动触发PendSV异常,在PendSV异常服务函数中实现任务切换。
freeRTOS有两种方式触发PendSV异常,一种是通过调用portYIELD()
, 另一种是在Systick_Handler()
中时基增加出现上下文切换请求。
触发PendSV异常的方法在task.h
中的
#define portYIELD() \
{ \
/*触发PendSV,产生上下文切换*/ \
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
}
以及port.c
中的
void xPortSysTickHandler( void )
{
/* 设置中断掩码,关中断 */
portDISABLE_INTERRUPTS();
{
/* Increment the RTOS tick. */
if( xTaskIncrementTick() != pdFALSE ) /* 检查就绪列表出现更高优先级的任务 */
{
/* 需要任务切换,产生PendSV中断 */
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
}
portENABLE_INTERRUPTS();
}
在portmacro.h
中定义了中断控制状态寄存器:portNVIC_INT_CTRL_REG
= 0xe000ed04。
其Bit 28 为PENDSVSET: portNVIC_PENDSVSET_BIT
:PendSV悬起位
因此,只要在portYIELD()
或者xPortSysTickHandler()
中调用了:
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
执行后就会进入PendSV异常,在PendSV中断服务函数中实现上下文切换。
异常(PendSV异常)发生时,R14保存异常返回标志,包括返回后进入任务模式还是处理模式、使用PSP还是MSP。
在PendSV异常保存上文时,通过向 R14 寄存器最后 4 位按位或上 0x0D,使得硬件在退出时使用进程堆栈指针 PSP 完成出栈操作,并在异常返回后进入任务模式、返回 Thumb 状态。然后将R14压入任务堆栈,一遍下次切换回该任务时获取R14的内容。
PendSV异常切换到下文时会将新任务堆栈中的R14读取出来,由于上文将R14做了保存,此时R14=0xFFFFFFFD,表示异常返回后进入任务模式,使用PSP作为堆栈指针出栈,出栈完毕后PSP指向任务栈栈顶。
在 stm32 中 PendSV 的异常服务函数名为 PendSV_Handler,在stm32xxxx_it.c中为弱定义,直接重定义函数即可。
在PendSV异常中执行四个步骤:
__asm volatile void xPortPendSVHandler( void )
{
extern pxCurrentTCB;
extern vTaskSwitchContext;
PRESERVE8
mrs r0, psp //R0=PSP
isb
ldr r3, =pxCurrentTCB /* Get the location of the current TCB. */
ldr r2, [r3] //R2=pxCurrentTCB
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} //入栈保存R3(即&pxCurrentTCB)和 R14
mov r0, %0
msr basepri, r0 //关闭中断
bl vTaskSwitchContext //在临界段切换就绪队列中优先级最高的任务,更新pxCurrentTCB
mov r0, #0
msr basepri, r0 //开启中断
ldmia sp!, {r3, r14} //从主堆栈中恢复寄存器R3和R14的值,此时SP使用的是MSP
ldr r1, [r3] //将R3存放的pxCurrentTCB的地址赋予R1
ldr r0, [r1] /* The first item in pxCurrentTCB is the task top of stack. */
ldmia r0!, {r4-r11} /* Pop the registers. */
msr psp, r0 //PSP=R0,更新PSP使异常退出时PSP为基地址进行其他寄存器的自动出栈,如下图1
isb
bx r14 //系统以PSP作为SP指针出栈,把新任务的任务堆栈中剩下的内容加载到CPU寄存器:R0(任务形参)、R1、R2、R3、R12、R14(LR)、R15(PC)和xPSR,切换到新任务,如图2
nop
}
汇编代码流程如下: