FreeRTOS源码理解(五)——任务切换

FreeRTOS源码理解(一)——FreeRTOS思路梳理

FreeRTOS源码理解(二)——中断和列表项

FreeRTOS源码理解(三)——任务

FreeRTOS源码理解(四)——任务调度器

FreeRTOS源码理解(五)——任务切换

FreeRTOS源码理解(六)——时间管理

FreeRTOS源码理解(七)——队列

FreeRTOS源码理解(八)——信号量

FreeRTOS源码理解(九)——事件组和任务通知

FreeRTOS源码理解(十)——软件定时器和空闲任务

FreeRTOS源码理解(十一)——内存管理

FreeRTOS源码理解(十二)——汇总

任务切换

任务堆栈与保护恢复现场

RTOS对每个任务均维护有一个堆栈,在进行任务调度时,RTOS会将任务的运行环境(程序指针,寄存器或变量的值等)信息压入堆栈中,当任务再次运行时,调度器会从而堆栈中取出数据,还原当时的运行环境;M3核有PSP:任务堆栈指针,会自动恢复部分寄存器的值

PendSV异常

PendSV(可挂起异常)与SVC不同,它是不精确的,可以子啊更高优先级的异常处理内挂起它,当更高优先级处理完成后PendSV执行。

FreeRTOS在PendSV中断中完成任务切换

任务切换的场合

  1. 系统调用

    #define taskYIELD()         portYIELD()
    #define portYIELD()																\
    {																				\
    	/* Set a PendSV to request a context switch. */								\
    	portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;								\
    																				\
    	/* Barriers are normally not required but do ensure the code is completely	\
    	within the specified behaviour for the architecture. */						\
    	__dsb( portSY_FULL_READ_WRITE );											\
    	__isb( portSY_FULL_READ_WRITE );											\
    }
    #define portNVIC_PENDSVSET_BIT		( 1UL << 28UL )
    #define portNVIC_INT_CTRL_REG		( * ( ( volatile uint32_t * ) 0xe000ed04 ) )
    

    将0xe000ed04地址的第28位置1,挂起一个PendSV中断

  2. 滴答定时器中断触发

    滴答定时器中断服务函数:

    void SysTick_Handler(void)
    {	
        //判断任务调度器状态
        if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)
        {
            xPortSysTickHandler();	
        }
    }
    
    void xPortSysTickHandler( void )
    {
    	//关中断
    	vPortRaiseBASEPRI();
    	{
    		//更新时钟节拍计数器,并检查是否有任务需要取消阻塞 xTaskIncrementTick
    		if( xTaskIncrementTick() != pdFALSE )
    		{
    			//挂起一个PendSV中断,通过操作寄存器
    			portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
    		}
    	}
        //开中断
    	vPortClearBASEPRIFromISR();
    }
    

    更新时钟节拍计数器,并检查是否有任务需要取消阻塞 xTaskIncrementTick

PendSV中断服务函数

__asm void xPortPendSVHandler( void )
{
	extern uxCriticalNesting;
	extern pxCurrentTCB;
	extern vTaskSwitchContext;

	PRESERVE8

	mrs r0, psp					//读取任务栈指针
	isb

	ldr	r3, =pxCurrentTCB		//获取当前任务TCB地址
	ldr	r2, [r3]

	stmdb r0!, {r4-r11}			//将寄存器r4-r11的值压入栈中
	str r0, [r2]				//将新的栈顶指针保存到r0

	stmdb sp!, {r3, r14}
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
	msr basepri, r0				//关中断
	dsb							
	isb							//数据和指令同步
	bl vTaskSwitchContext		//加载函数,获取下一个要运行的任务,并更新pxCurrentTCB
	mov r0, #0
	msr basepri, r0				//开中断
	ldmia sp!, {r3, r14}

	ldr r1, [r3]
	ldr r0, [r1]				//获取新任务的任务栈顶
	ldmia r0!, {r4-r11}			//恢复寄存器r4-r11的值
	msr psp, r0					//将新的栈顶赋值给任务栈指针,其他的寄存器值会自动恢复
	isb
	bx r14
	nop
}

获取下一个要运行的任务

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

		//获取下一个要运行的任务
		taskSELECT_HIGHEST_PRIORITY_TASK();
		traceTASK_SWITCHED_IN();
	}
}
  • 获取下一个要运行的任务分为通用方法和硬件方式

通用方法

#define taskSELECT_HIGHEST_PRIORITY_TASK()															\
{																									\
    UBaseType_t uxTopPriority = uxTopReadyPriority;														\
    /* Find the highest priority queue that contains ready tasks. */								\
    while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) )							\
    {																								\
        configASSERT( uxTopPriority );																\
        --uxTopPriority;																			\
    }																								\

    /* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of						\
    the	same priority get an equal share of the processor time. */									\
    listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );			\
    uxTopReadyPriority = uxTopPriority;																\
} /* taskSELECT_HIGHEST_PRIORITY_TASK */

uxTopReadyPriority存储就绪列表的最高优先级,从就绪列表数组中取出一个任务块,将其赋给pxCurrentTCB,移动当前列表指针

硬件方法

#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() */

portGET_HIGHEST_PRIORITY:获取就绪态的最高优先级

从就绪列表数组中取出一个任务块,将其赋给pxCurrentTCB

优先级数量受位的影响,因为优先级用位图表示

时间片轮转

启用了时间片轮转的宏时,在滴答定时器中断中会对就绪列表中当前优先级任务数量进行判断,如果其他任务则进行任务切换

文档汇总

文档汇总

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值