FreeRTOS系列--任务切换

   日期:2021-03-26     浏览:110    评论:0    
核心提示:处理器执行一条语句流程arm M3寄存器中断基本汇编语句任务切换流程任务切换源码分析左对齐右对齐居中对齐单元格单元格单元格单元格单元格单元格任务切换会用到汇编,因此常见的汇编指令需要了解1:内存读写指令ldr 指令 将数据读取到寄存器mrsmrsstmdb 将寄存器压栈ldmia 将栈数据弹出str 指令 STR指令用亍从源寄存器中将一个32位的字数据传送到存储器中MOV指令2:运算指令3:跳转分支4:比较context_m3

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 )
	{ 
		
		xYieldPending = pdTRUE;
	}
	else
	{ 
		xYieldPending = pdFALSE;
		traceTASK_SWITCHED_OUT();

		
		taskCHECK_FOR_STACK_OVERFLOW();

		
	   //查找最高优先级任务
		taskSELECT_HIGHEST_PRIORITY_TASK();
		traceTASK_SWITCHED_IN();
		#if ( configUSE_NEWLIB_REENTRANT == 1 )
		{ 
			
			_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
		}
		#endif 
	}
}


	#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 listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \ { \ List_t * const pxConstList = ( pxList ); \ 				\
		
	//得到上一个任务的链表节点 \
	( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;							\
	if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )	\
	{ 																						\
		( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;						\
	}
	//返回下一个链表的TCB \
	( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;											\
}

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

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
更多>相关资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服