资讯详情

vTaskSwitchContext

1. 任务切换相关API函数

函数 描述
xPortPendSVHandler() PendSV事实上,中断服务函数的函数原型是PendSV_Handler()
vTaskSwitchContext() 检查任务堆栈是否溢出,并找到下一个优先级高的任务。如果能够统计运行时间,则会计任务运行时间

2. 任务切换的基本知识

在FreeRTOS在任务管理中,最重要的目的是找到最高的线索优先级任务,然后执行任务切换,以保持最高的优先级任务被占用CPU资源。使用汇编代码编写任务切换部分程序,以达到最佳性能。

FreeRTOS触发任务切换的方法有两种:

  • 中断系统节拍时钟(SysTick定时器)。切换过程参考。《FreeRTOS原理剖析:系统节拍时钟分析》
  • 执行系统调用代码。使用普通任务taskYIELD()强制任务切换;中断服务程序使用;portYIELD_FROM_ISR()强制任务切换;也可以在应用程序中设置xYieldPending通知调度器切换任务。

其中:

// 对于普通任务 #define taskYIELD()    portYIELD() #define portYIELD_WITHIN_API  portYIELD 
// 中断服务程序 #define portEND_SWITCHING_ISR( xSwitchRequired ) if( xSwitchRequired != pdFALSE ) portYIELD() #define portYIELD_FROM_ISR( x ) portEND_SWITCHING_ISR( x ) 

可此可见,最终执行的代码段为:

#define portYIELD()           \ {               \  /*               \   * 通过向中断控制和状态寄存器ICSR第28位写入1,触发PendSV中断 \   * 地址为0xE000 ED04          \   */              \  portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;   \                \  /* dsb和isb 完成数据同步隔离和指令同步隔离     \   * 确保以前的存储器访问操作和指令完成      \   */              \  __dsb( portSY_FULL_READ_WRITE );      \  __isb( portSY_FULL_READ_WRITE );      \ } 

通过向中断控制和状态寄存器ICSR(地址:0xE000 ED04)第28位写入1,触发PendSV中断执行任务切换。

3. 任务切换过程

3.1 PendSV中断服务函数分析

在FreeRTOS中,有:

#define xPortPendSVHandler  PendSV_Handler 

源代码如下:

__asm void xPortPendSVHandler( void ) {  extern uxCriticalNesting;  extern pxCurrentTCB;  /* 它将永远指向当前激活的任务 */  extern vTaskSwitchContext; 
PRESERVE8  mrs r0, psp     /* 阅读过程栈指针PSP,保存在R0中,此时SP的值为MSP */ isb       /* 指令同步隔离 */  /* 这两句使R2.保存当前激活的任务TCB首地址 */ ldr r3, =pxCurrentTCB  /* 将pxCurrentTCB存储地址保存到R3,注意的是pxCurrentTCB存储地址固定不变,但方向可变 */ ldr r2, [r3]    /* 将R3地址所在的数据保存R2,即TCB的首地址 */  /*   * 判断前两句是否有效FPU,如果可以,手动操作s16~s31压入栈中  * 其中s0~s15和FPSCR自动完成硬件   */ tst r14, #0x10 it eq vstmdbeq r0!, {s16-s31}  /* 将当前激活任务的寄存器值入堆栈,并更新R此外,硬件自动将xPSR、PC、LR、R12、R0~R3入栈 */ stmdb r0!, {r4-r11, r14}   /* R0为PSP地址,R二是激活任务TCB地址,R0的值写入R2保存地址去,即TCB第一个成员指向线程堆栈指针,每次任务切换都会更新PSP */ str r0, [r2]      stmdb sp!, {r3}    /* 将R3临时压入堆栈,R3保存了pxCurrentTCB地址,函数调用后会使用,所以要进入栈保护 */  /* 中断优先级大于或等于configMAX_SYSCALL_INTERRUPT_PRIORITY中断将被屏蔽 */ mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY  msr basepri, r0      dsb       /* 数据同步隔离 */ isb       /* 指令同步隔离 */  bl vTaskSwitchContext  /* 切换到vTaskSwitchContext,找下一个任务 */  /* 开中断 */  mov r0, #0  msr basepri, r0      ldmia sp!, {r3}    /* 恢复R3,R3保存了pxCurrentTCB的地址,这里pxCurrentTCB地址是固定的,但改变了 */  /* 当前激活的TCB栈顶值存入R0 */ ldr r1, [r3]    /* 将pxCurrentTCB指向地址赋值R1,即将TCB首地址赋值R1 */ ldr r0, [r1]    /* 将R1地址所在的数据赋值R0,即当前激活任务TCB第一项的值赋予R0 */  ldmia r0!, {r4-r11, r14} /* 将寄存器R4~R11出栈,同时更新R0的值 */   /*   * 判断前两句是否有效FPU,如果可以,手动恢复s16-s31浮点寄存器  * 其中s0~s15和FPSCR自动完成硬件   */ tst r14, #0x10 it eq vldmiaeq r0!, {s16-s31}  msr psp, r0     /* 将最新任务堆栈的顶部赋值线程堆栈指针PSP */ isb       /* 指令同步隔离,清流水线 */  #ifdef WORKAROUND_PMU_CM001  #if WORKAROUND_PMU_CM001 == 1   push { r14 }   pop { pc }   nop  #endif #endif  bx r14 /* 当调用 bx r14指令退出中断,堆栈指针PSP指向新任务堆栈的正确位置 */ 

}

说明:

  • 在Cortex-M处理器中有两个栈指针,一个是主栈指针(Main Stack Pointer,即MSP),它可以用于线程模式,只能用于中断模式MSP;另一个是过程堆栈指针(Processor Stack Pointer,即PSP),PSP总是用于线程模式。它只能在任何时候使用。
  • 复位后处于线程模式特权级,默认使用MSP。在FreeRTOS中,MSP用于OS内核和异常处理,PSP用于应用任务。
  • 通过设置CONTROL寄存器的bit[1]选择使用哪个堆栈指针。CONTROL[1]=0选主堆栈指针;CONTROL[1]=1择过程堆栈指针。

3.2 函数vTaskSwitchContext()

该函数将更新当前任务运行时间,检查任务堆栈是否溢出,然后调用宏 taskSELECT_HIGHEST_PRIORITY_TASK()获得更高优先级的任务。

函数源代码如下:

void vTaskSwitchContext( void ) {  /* 如果任务调度器已经挂起 */  if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )  {   xYieldPending = pdTRUE;  /* 标记任务调度器不允许任务切换 */  }  else  {   xYieldPending = pdFALSE; /* 任务调度器未挂起 */ 
 traceTASK_SWITCHED_OUT();   /*    * 设置运行时间统计功能configGENERATE_RUN_TIME_STATS为1   * 如果使用了该功能,以下两个宏:   * portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()    portGET_RUN_TIME_COUNTER_VALUE()
	 */
	#if ( configGENERATE_RUN_TIME_STATS == 1 )
	{
			#ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
				portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );
			#else
				ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
			#endif

			/*  
			 * ulTotalRunTime记录系统的总运行时间,ulTaskSwitchedInTime记录任务切换的时间
			 * 如果系统节拍周期为1ms,则ulTotalRunTime要497天后才会溢出
			 * ulTotalRunTime < ulTaskSwitchedInTime表示可能溢出
			 */
			if( ulTotalRunTime > ulTaskSwitchedInTime )
			{
				/* 记录当前任务的运行时间 */
				pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}

			/* 更新ulTaskSwitchedInTime,下个任务时间从这个值开始 */
			ulTaskSwitchedInTime = ulTotalRunTime;	
	}
	#endif /* configGENERATE_RUN_TIME_STATS */

	/* 核查堆栈是否溢出 */
	taskCHECK_FOR_STACK_OVERFLOW();

	/* 寻找更高优先级的任务 */
	taskSELECT_HIGHEST_PRIORITY_TASK();
	
	traceTASK_SWITCHED_IN();

	/* 如果使用Newlib运行库,你的操作系统资源不够,而不得不选择newlib,就必须打开该宏 */
	#if ( configUSE_NEWLIB_REENTRANT == 1 )
	{
		_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
	}
	#endif /* configUSE_NEWLIB_REENTRANT */
}

}

3.2 寻找下一个任务的方式

PendSV中会调用vTaskSwitchContext(),最后调用函数taskSELECT_HIGHEST_PRIORITY_TASK()寻找优先级最高的任务。

对于FreeRTOS的调度器,它有两种方式寻找下一个最高优先级的任务,分别为特殊方式和常用方式,在FreeRTOSConfig.h中可通过宏定义设置,如下:

/* 0:使用常用方式来选择下一个要运行的任务;1:使用特殊方法来选择下一个要运行的任务 */
#define configUSE_PORT_OPTIMISED_TASK_SELECTION	1

在FreeRTOS中,通用方法不依赖某些硬件等限制,适用于多种MCU中。特殊方式是使用了某些硬件的特性,只针对部分MCU而使用。

3.2.1 常用方法

uxTopReadyPriority 记录就绪态中最高优先级值,创建任务时会更新值,有任务添加到就绪表时也会更新值。这种方法对任务的数量无限制。

#define taskSELECT_HIGHEST_PRIORITY_TASK()												\
{																						\
	UBaseType_t uxTopPriority = uxTopReadyPriority;										\
																						\
	while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) )				\
	{																					\
		configASSERT( uxTopPriority );													\
		--uxTopPriority;																\
	}																					\
																						\
	/* 获取优先级最高任务的任务控制块 */														\
	listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );\
uxTopReadyPriority = uxTopPriority;	 												\

}

3.2.2 特殊方式

使用此方法,uxTopReadyPriority 每个bit位表示一个优先级,bit0表示优先级0,bit31表示优先级31,使用此方式优先级最大只能是32个。

#define taskSELECT_HIGHEST_PRIORITY_TASK()														\
{																								\
	UBaseType_t uxTopPriority;																		\
																								\
	/* 获取优先级最高的任务 */								\
	portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );								\
	configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 );		\
/* 获取优先级最高任务的任务控制块 */
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );		\

}

其中:

#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )

__clz( ( uxReadyPriorities ) 是计算uxReadyPriorities 的前导零个数,如: 二进制0001 1010 0101 1111的前导零个数为3,可以知道,最高优先级uxTopPriority 等于 31减去前导零个数。 知道最高优先级的优先级,则通过listGET_OWNER_OF_NEXT_ENTRY()对应最高优先级的列表项,将pxCurrentTCB指向对应的控制块。


参考资料:

【1】: 正点原子:《STM32F407 FreeRTOS开发手册V1.1》 【2】: 野火:《FreeRTOS 内核实现与应用开发实战指南》 【3】: 《Cortex M3权威指南(中文)》

标签: 4394s15ac接近传感器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台