FreeRTOS笔记(五):任务切换

FreeRTOS笔记(五):任务切换

本章是FreeRTOS系统的核心内容,更是面试求职过程中重要的考点。对于实时操作系统,任务切换决定了任务执行顺序,任务切换也决定了效率的高低。下面将详细介绍任务切换的内容。

一、PendSV中断

任务切换是依靠PendSV中断(可挂起的系统调用)来实现的,因此PendSV中断对操作系统来说是重点。

PendSV中断是可编程中断,触发条件是:将中断控制和状态寄存器ICSR的bit28置为1。与SVC不同,它是不精准的,因此它的挂起状态可在更高优先级中断中处理内设置,而且会在更高优先级处理完后执行。

利用此特性,将PendSV设置为最低优先级,在其他中断执行完后,在进行任务切换。

在典型的嵌入式系统中,处理时间将会被划分多个片段,假设只有两个任务,任务将交替执行,如下图:

FreeRTOS笔记(五):任务切换_第1张图片

此时上下文切换场合为:

1.执行系统调用;

2.系统滴答定时器中断(SysTick)。

若中断请求(IRQ)在SysTick中断之前产生,则SysTick中断会抢占IRQ的处理,IRQ处理没有结束,此时不应该进行任务切换,否则IRQ处理就会被延迟,这是不能容忍的。下图是IRQ延迟:

FreeRTOS笔记(五):任务切换_第2张图片

为了解决这个问题,PendSV将上下文切换延迟到IRQ处理完了以后,PendSV需要将优先级设置为最低。PendSV任务切换如下图:

FreeRTOS笔记(五):任务切换_第3张图片
FreeRTOS笔记(五):任务切换_第4张图片

二、任务切换场合

在上一节中提到了任务切换的场合:

1.执行系统调用;

2.系统滴答定时器中断(SysTick)。

2.1 执行系统调用

系统调用就是执行FreeRTOS能够进行任务切换的API,比如taskYIELD(),调用taskYIELD()函数的一些API也称为系统调用。taskYIELD()具体如下:

#define taskYIELD()					portYIELD()		

#define portYIELD()												\
{																\
																\
	portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;				\  //-----------(1)
																\
	__dsb( portSY_FULL_READ_WRITE );							\
	__isb( portSY_FULL_READ_WRITE );							\

}

(1)将ICSR的bit28置为1,触发中断,就可以在PendSV中断服务程序中进行任务切换。

2.2 SysTick中断

FreeRTOS的滴答定时器中断服务函数也能进行任务切换,服务程序如下:

void SysTick_Handler(void)
{	
    if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已运行
    {
        xPortSysTickHandler();
    }
}

SysTick_Handler又调用了xPortSysTickHandler()函数来实现切换:

void xPortSysTickHandler( void )
{
	
	vPortRaiseBASEPRI();    //-----------------(1)
	{
		if( xTaskIncrementTick() != pdFALSE )
		{
			
			portNVIC_INT_CTRL_REG = \
					portNVIC_PENDSVSET_BIT;  //-----------------(2)
		}
	}
	vPortClearBASEPRIFromISR();           //-----------------(3)
}

(1)关闭中断;

(2)将ICSR的bit28置为1,挂起PendSV来启动PendSV中断;

(3)打开中断。

从以上两个场合可以看出,在FreeRTOS进行任务切换都是通过触发PendSV中断来实现。

三、PendSV中断服务程序

终于来到了PendSV中断服务程序PendSV_Handler(),FreeRTOS任务的切换都是在这里完成的,下面将详细介绍。

__asm void xPortPendSVHandler( void )
{
	extern uxCriticalNesting;
	extern pxCurrentTCB;            /* 指向当前激活的任务 */
	extern vTaskSwitchContext;      

	PRESERVE8

	mrs r0, psp                   /* PSP内容存入R0 */    
	isb                           /* 指令同步隔离,清流水线 */

	ldr	r3, =pxCurrentTCB     /* 当前激活的任务TCB指针存入R2 */
	ldr	r2, [r3]

	stmdb r0!, {r4-r11}          /* 保存剩余的寄存器,异常处理程序执行前,硬件自动将xPSR、PC、LR、R12、R0-R3入栈 */
	str r0, [r2]		     /* 将新的栈顶保存到任务TCB的第一个成员中 */

	stmdb sp!, {r3, r14}         /* 将R3和R14临时压入堆栈,因为即将调用函数vTaskSwitchContext,调用函数时,返回地址自动保存到R14中,
									所以一旦调用发生,R14的值会被覆盖,因此需要入栈保护; R3保存的当前激活的任务TCB指针(pxCurrentTCB)地址,
									函数调用后会用到,因此也要入栈保护*/
									
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY   /* 进入临界区 */
	msr basepri, r0
	dsb                         /* 数据和指令同步隔离 */
	isb
	bl vTaskSwitchContext        /* 调用函数,寻找新的任务运行,通过使变量pxCurrentTCB指向新的任务来实现任务切换 */
	mov r0, #0                   /* 退出临界区*/
	msr basepri, r0
	ldmia sp!, {r3, r14}         /* 恢复R3和R14*/

	ldr r1, [r3]
	ldr r0, [r1]		     /* 当前激活的任务TCB第一项保存了任务堆栈的栈顶,现在栈顶值存入R0*/
	ldmia r0!, {r4-r11}	     /* 出栈*/
	msr psp, r0
	isb
	bx r14                      /* 异常发生时,R14中保存异常返回标志,包括返回后进入线程模式还是处理器模式、
								使用PSP堆栈指针还是MSP堆栈指针,当调用 bx r14指令后,硬件会知道要从异常返回,然后出栈,
							    这个时候堆栈指针PSP已经指向了新任务堆栈的正确位置,
							    当新任务的运行地址被出栈到PC寄存器后,新的任务也会被执行。*/
	nop
}

FreeRTOS笔记(五):任务切换_第5张图片

主堆栈指针MSP:对于 Cortex-M3 硬件,当系统复位后,默认使用 MSP 指针。MSP 指针用于操作系统内核以及处理异常(也就是说中断服务程序中默认强制使用 MSP 指针,这是硬件自动设置的)。

进程堆栈指针PSP:任务(进程)使用 PSP 指针,操作系统负责从 MSP 指针切换到 PSP 指针。

任务堆栈:每个任务都有自己的“任务堆栈”,在任务创建时会创建指定大小的任务堆栈,这是任务能够独立运行的前提条件之一。在任务中定义的局部变量,会优先使用寄存器,寄存器不够时就使用任务堆栈的空间。如果在任务中调用其它函数,则调用前的保存信息也存到任务堆栈中去。

堆栈:操作系统内核以及异常处理程序中使用 MSP 指针,所以它们也需要一个堆栈空间,我们称之为“堆栈”,这个堆栈空间和任务堆栈空间在物理上是绝对不可以重叠的。

FreeRTOS笔记(五):任务切换_第6张图片

以上介绍了几个重要的概念,下面详析中断服务函数。

mrs r0, psp 

将任务堆栈指针 PSP 的值保存到寄存器 R0中。

ldr	r3, =pxCurrentTCB		    /* 当前激活的任务TCB指针存入R2 */
ldr	r2, [r3]

获取当前激活的任务 TCP 指针。

 r0!, {r4-r11}

将寄存器 R4~R11 保存到当前激活的程序任务堆栈中,并且同步更新寄存器R0的值。

str r0, [r2]

寄存器 R2 中保存当前激活的任务 TCB 指针。

stmdb sp!, {r3, r14}

将R3和R14 临时压入堆栈。调用函数时,返回地址自动保存到R14 中,所以一旦调用发生,R14 的值会被覆盖,因此需要入栈保护。R3 保存的当前激活的任务 TCB 指针(pxCurrentTCB)地址,函数调用后会用到,因此也要入栈保护。

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]	

获取变量pxCurrentTCB指向的任务TCB 指针,并将 TCB 的第一个成员——当前堆栈栈顶的指针变量pxTopOfStack的值保存到寄存器R0 中,也就是将即将运行的任务堆栈栈顶值存入R0。

ldmia r0!, {r4-r11}

将寄存器 R4~R11 出栈,并同时更新 R0 的值。

msr psp, r0

将最新的任务堆栈栈顶赋值给线程堆栈指针 PSP。

bx r14

从异常中断服务程序退出,任务切换完成。

四、查找下一个要运行的任务

在PendSV中断服务函数中,调用vTaskSwitchContext()来找到下一个要运行的任务,这是任务切换的核心。

void vTaskSwitchContext( void )
{
	if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )  //-----------------(1)
	{
		
		xYieldPending = pdTRUE;
	}
	else
	{
		xYieldPending = pdFALSE;
		traceTASK_SWITCHED_OUT();
		taskCHECK_FOR_STACK_OVERFLOW();

		taskSELECT_HIGHEST_PRIORITY_TASK();    //-----------------(2)
		traceTASK_SWITCHED_IN();
	}
}

(1)任务调度器挂起不能进行任务切换;

(2)获取下一个要运行的任务。

查找下一个要运行的任务有两种方法:通用方法和硬件的方法。

4.1通用方法

● configUSE_PORT_OPTIMISED_TASK_SELECTION 设置为 0 ;

● 可以用于所有 FreeRTOS 支持的硬件;

● 完全用 C 实现,效率略低于特殊方法;

● 不强制要求限制最大可用优先级数目。

#define taskSELECT_HIGHEST_PRIORITY_TASK()							\
{																	\
	/* 从就绪列表数组中找出最高优先级列表*/							\
	while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopReadyPriority ])))	\
	{																\
		configASSERT( uxTopReadyPriority );							\
		--uxTopReadyPriority;										\
	}																\
																	\
	/* 相同优先级的任务使用时间片共享处理器就是通过这个宏实现*/  	\
	listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, &( pxReadyTasksLists[ uxTopReadyPriority ] ) );   \
} 

4.2 硬件方法

● 并非所有硬件都支持;

● 必须将 configUSE_PORT_OPTIMISED_TASK_SELECTION 设置为 1;

● 依赖一个或多个特定架构的汇编指令(一般是类似计算前导零 [CLZ] 指令);

● 比通用方法更高效;

● 一般强制限定最大可用优先级数目为 32(0~31)。

#define taskSELECT_HIGHEST_PRIORITY_TASK()							\
{																	\
	UBaseType_t uxTopPriority;										\
																	\
	/* 从就绪列表数组中找出最高优先级列表*/          					\
	portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );	\
	listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); 											\
}

参考文献

https://blog.csdn.net/qq_27114397/article/details/83017512

你可能感兴趣的:(堆栈,操作系统,嵌入式,单片机)