【FreeRTOS】3. PendSV异常

PendSV异常

问题:

  1. 怎么触发PendSV异常?
  2. 何时使用MSP何时切换PSP?
  3. PendSV如何实现上下文切换?
1. 触发PendSV异常

在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悬起位

image-20211112215504221

因此,只要在portYIELD()或者xPortSysTickHandler()中调用了:

portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;

执行后就会进入PendSV异常,在PendSV中断服务函数中实现上下文切换。

2. 堆栈指针的切换

异常(PendSV异常)发生时,R14保存异常返回标志,包括返回后进入任务模式还是处理模式、使用PSP还是MSP。

在PendSV异常保存上文时,通过向 R14 寄存器最后 4 位按位或上 0x0D,使得硬件在退出时使用进程堆栈指针 PSP 完成出栈操作,并在异常返回后进入任务模式、返回 Thumb 状态。然后将R14压入任务堆栈,一遍下次切换回该任务时获取R14的内容。

PendSV异常切换到下文时会将新任务堆栈中的R14读取出来,由于上文将R14做了保存,此时R14=0xFFFFFFFD,表示异常返回后进入任务模式,使用PSP作为堆栈指针出栈,出栈完毕后PSP指向任务栈栈顶。

3. PendSV中断实现上下文切换

在 stm32 中 PendSV 的异常服务函数名为 PendSV_Handler,在stm32xxxx_it.c中为弱定义,直接重定义函数即可。

在PendSV异常中执行四个步骤:

  1. 关闭中断
  2. 保存上文
  3. 加载下文
  4. 打开中断
__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
}

汇编代码流程如下:

CSDN图片

图1:
【FreeRTOS】3. PendSV异常_第1张图片

图2:
【FreeRTOS】3. PendSV异常_第2张图片

你可能感兴趣的:(RTOS,stm32,rtos,freertos)