FreeRTOS系列--任务切换

FreeRTOS任务切换涉及到芯片架构以及汇编代码,因此 这里将使用Contex_M3为例子,这里将从contex_M3寄存器组,汇编处理a=a+b流程,基本的汇编语句,PendSV,PendSV中断 源码,查找最高优先级任务共6个部分来介绍FreeRTOS的任务切换。

contex_M3寄存器组

如我们所见,CM3 拥有通用寄存器 R0‐R15 以及一些特殊功能寄存器。R0‐R12 是最“通用目的”的,但是绝大多数的 16 位指令只能使用 R0‐R7(低组寄存器),而 32 位的 Thumb‐2指令则可以访问所有通用寄存器。特殊功能寄存器有预定义的功能,而且必须通过专用的指令来访问。
在这里插入图片描述

  • 堆栈指针 R13
    CM3拥有两个堆栈指针,分别是MSP(主堆栈指针),PSP(进程堆栈指针),MSP主要用于中断,异常和mian函数;PSP主要用于我们自己建立的任务使用,我们每次调用xTaskCreate函数时,都会传入一个申请空间大小,这里的大小就是任务栈大小,每一个任务都有自己独享的任务栈,每次进行任务切换得 时候,都会当前任务使用到的个R0-R15寄存器的值存入当前的任务栈。
  • 连接寄存器 R14
    连接寄存器 R14一般有两个用途:一是用来保存子程序返回地址;二是当异常发生时,LR中保存的值等于异常发生时PC的值减4(或者减2),因此在各种异常模式下可以根据LR的值返回到异常发生前的相应位置继续执行。

汇编处理a=a+b流程

Contex_M3本身使用精简指令,ARM指令有如下特点:

  1. 对内存只有读写指令,
  2. 对数据运算都是在CPU内部实现
  3. 精简指令的CPU比较容易设计
    在这里插入图片描述

上图所示为c语言代码 a= a + b转换为汇编的情况,第1,2,4均为读写内存指令。

基本汇编语句

左对齐 右对齐 居中对齐
mrs r0, psp 读取特殊寄存器指令到寄存器 读取PSP寄存器的值到R0
msr psp, r0 写寄存器到特殊寄存器指令 写R0寄存器到PSP
ldr r3, =pxCurrentTCB 读取 pxCurrentTCB的地址到R3 类似c语言 R3 = &pxCurrentTCB
ldr r3, [r2] 对R2指向的地址取值 类似c语言 R3 = *R2
stmdb r0!, {r4-r11} 压入堆栈 将R4-R11的值拷贝到R0的地址里面
ldmia sp!, {r3, r14} 压出堆栈 将默认堆栈指针的数据拷贝到R3-R14寄存器
bl vTaskSwitchContext 跳转指令
mov r0, #0 写入立即数0 类似c语言包R0 = 0

PendSV中断

FreeRTOS任务切换是在PendSV里面执行的,PendSV主要特点是它是可以像普通的中断一样被悬起的。OS 可以利用它“缓期执行”一个异常——直到其它重要的任务完成后才执行动作。悬 起 PendSV 的方法是:手工往 NVIC 的 PendSV 悬起寄存器中写 1。悬起后,如果PendSV优先级不够高,则将缓期等待执行。利用 PendSV执行上下文切换如下:
在这里插入图片描述
触发PendSV中断一般两种情况:

  1. sysTick到了任务切换得时间点,在sysTick中断中触发PendSV中断。
  2. 在函数中进行触发PendSV中断。

PendSV源码分析

__asm void xPortPendSVHandler( void )
{
    
    
	extern uxCriticalNesting;
	extern pxCurrentTCB;
	extern vTaskSwitchContext;
	//告诉编译器以精简指令运行
	PRESERVE8
	//读取任务栈指针到r0
	mrs r0, psp
	isb
	//获取当前任务控制块的指针
	ldr	r3, =pxCurrentTCB	
	//获取当前任务控制块的指针	指向的第一个元素 及pxTopOfStack的值
	ldr	r2, [r3]
	//将寄存器R3-R11的值压入当前任务栈
	stmdb r0!, {
    
    r4-r11}			
	//将新的任务栈亚地址存入 当前TCB第一个地址
	str r0, [r2]				
	//将r3 和r14压入主堆栈
	stmdb sp!, {
    
    r3, r14}  
	//将configMAX_SYSCALL_INTERRUPT_PRIORITY写入r0
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
	//将r0的值写入basepri  进入临界区
	msr basepri, r0
	dsb
	isb
	//跳转查找优先级最高的任务
	bl vTaskSwitchContext
	//r0写入立即数0
	mov r0, #0
	//将r0的0写入basepri  退出临界区
	msr basepri, r0
	//将r3和r14压出堆栈
	ldmia sp!, {
    
    r3, r14}

	//r3存入的是pxCurrentTCB 的指针地址  刚才已经查找到新的pxCurrentTCB ,此时将pxCurrentTCB 读入r1
	ldr r1, [r3]
	//得到新的TCB的第一个元素 及pxTopOfStack的 栈地址
	ldr r0, [r1]		
	//出栈到新的任务栈	
	ldmia r0!, {
    
    r4-r11}		
	//将新的任务栈地址 写入PSP寄存器
	msr psp, r0
	isb
	//退出挡墙中断
	bx r14
	nop
}

上面代码为一个任务切换过程,从中可以发现,只进行了R4-R11寄存器的压栈处理,并没有进行R0-R13压栈处理,那是因为CM3内核在进入或者退出中断时会自动将R0-R3 R12 LR PC等寄存器压入堆栈,然后在退出中断 自动恢复。

查找最高优先级任务

void vTaskSwitchContext( void )
{
    
    
	if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
	{
    
    
		/* The scheduler is currently suspended - do not allow a context
		switch. */
		xYieldPending = pdTRUE;
	}
	else
	{
    
    
		xYieldPending = pdFALSE;
		traceTASK_SWITCHED_OUT();

		/* Check for stack overflow, if configured. */
		taskCHECK_FOR_STACK_OVERFLOW();

		/* Select a new task to run using either the generic C or port
		optimised asm code. */
	   //查找最高优先级任务
		taskSELECT_HIGHEST_PRIORITY_TASK();
		traceTASK_SWITCHED_IN();
		#if ( configUSE_NEWLIB_REENTRANT == 1 )
		{
    
    
			/* Switch Newlib's _impure_ptr variable to point to the _reent
			structure specific to this task. */
			_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
		}
		#endif /* configUSE_NEWLIB_REENTRANT */
	}
}


	#define taskSELECT_HIGHEST_PRIORITY_TASK()														\
	{																								\
	UBaseType_t uxTopPriority;																		\
																									\
		/* Find the highest priority list that contains ready tasks. */								\        
		//查找当前最有就绪态的高优先级
		portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );								\
		configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 );		\
		//得到一个最高优先级下挂载的就绪任务
		listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );		\
	} /* taskSELECT_HIGHEST_PRIORITY_TASK() */
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )										\
{																							\
List_t * const pxConstList = ( pxList );													\
	/* Increment the index to the next item and return the item, ensuring */				\
	/* we don't return the marker used at the end of the list.  */	
	//得到上一个任务的链表节点						\
	( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;							\
	if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )	\
	{
    
    																						\
		( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;						\
	}
	//返回下一个链表的TCB																						\
	( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;											\
}

从上可至,每一个优先级下面有一个就绪态任务TCB一个链表,每次切换任务都会取一个任务的指针,如果同一个优先级下有多个就绪态的任务,则会依次调用。

猜你喜欢

转载自blog.csdn.net/yuanlin725/article/details/115121316