对FreeRTOS的深入理解(四)

        接上文主干内容,我们需要对PendSV内部的源码进行分析,才能更好的得出FreeRTOS是怎么真正执行任务切换的,他怎么知道我下一个任务是什么且任务调用栈的地址,以及如何保护现场、恢复现场?

        为此我们找到PendSV的中断函数源码如下,对应汇编指令的用途已经备注好:

//PendSV内部实现
__asm void xPortPendSVHandler( void )
{
	extern uxCriticalNesting;
	extern pxCurrentTCB;
	extern vTaskSwitchContext;

    //8字节对齐
	PRESERVE8
	
    //将PSP指针保存在r0寄存器中,PSP指针保存的时正在执行任务的任务栈指针
	mrs r0, psp
	isb
	
    //r3保存当前TCB的地址
	ldr	r3, =pxCurrentTCB
        
    //栈顶指针的地址(结构体第一个成员的地址和结构体的地址相同)
    //&栈顶指针的地址 ---> 被分配内存的栈顶地址
	ldr	r2, [r3]
	
    //压栈操作,r0寄存器保存的指针向下移动,将任务信息保存到内存分配的任务堆栈中
	stmdb r0!, {r4-r11}	
    
    //把栈顶指针的值修改为r0寄存器保存的指针指向的位置,利于恢复任务操作
	str r0, [r2]				

/*----------------------------以上为保存现场操作--------------------------*/
        
/*----------------------------以下为恢复现场操作--------------------------*/
    //将r3、r14保存到MSP指针中,压栈操作
    stmdb sp!, {r3, r14}
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
	msr basepri, r0
	dsb
	isb
    
    //通过 vTaskSwitchContext 获取当前优先级最高的任务
	bl vTaskSwitchContext										
	mov r0, #0
	msr basepri, r0
	ldmia sp!, {r3, r14}
	
    //获取当前任务(任务切换后)的任务栈顶
	ldr r1, [r3]
	ldr r0, [r1]
    
    //出栈,将内存中保存的任务信息恢复到CPU寄存器中
	ldmia r0!, {r4-r11}			
	msr psp, r0
	isb
	bx r14
	nop
}

        对此过程,我们需要结合一张图去理解,如下为保存现场:

 那么恢复现场就是相反的操作,把任务栈中的信息全部pop进CPU的内部寄存器中执行,那么最重要的其实就是代码中找到下一个优先级最高的任务,这里就是vTaskSwitchContext这个API,源码如下:

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();

		#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

				/* Add the amount of time the task has been running to the
				accumulated time so far.  The time the task started running was
				stored in ulTaskSwitchedInTime.  Note that there is no overflow
				protection here so count values are only valid until the timer
				overflows.  The guard against negative values is to protect
				against suspect run time stat counter implementations - which
				are provided by the application, not the kernel. */
				if( ulTotalRunTime > ulTaskSwitchedInTime )
				{
					pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
				ulTaskSwitchedInTime = ulTotalRunTime;
		}
		#endif /* configGENERATE_RUN_TIME_STATS */

		/* 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 */
	}
}

其中,taskSELECT_HIGHEST_PRIORITY_TASK,就是来寻找优先级最高的那个任务,并把下一个优先级最高(要运行的任务)TCB赋给pxCurrentTCB指针上,之后再通过获取当前任务的栈顶指针并将该任务现场还原。这样就达到了任务切换的目的。

        本篇文章接着上文,将任务切换的具体流程解释明白了。总结一下,还是由上文所说的主干开始,当滴答定时器中断后,FreeRTOS会记录系统时钟、根据系统时钟置换阻塞列表、将阻塞列表中过期任务解挂放入就序列表、更新下一次任务解挂阻塞时间、标记是否需要任务切换、如果需要任务切换最后启用PendSV中断进行任务的切换。

  • 15
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值