FreeRTOS基础(四):任务创建和切换

FreeRTOS 为了任务启动和任务切换使用了三个异常:SVC、PendSV 和 SysTick。

SVC(系统服务调用)用于任务启动,有些操作系统不允许应用程序直接访问硬件,而是通过提供一些系统服务函数,通过 SVC 来调用;

PendSV(可挂起系统调用)用于完成任务切换,它的最大特性是如果当前有优先级比它高的中断在运行,PendSV 会推迟执行,直到高优先级中断执行完毕;

SysTick 用于产生系统节拍时钟,提供一个时间片,如果多个任务共享同一个优先级,则每次 SysTick 中断,下一个任务将获得一个时间片。

一、任务创建

1.1 TCB控制块

创建任务的时候,首先申请堆内存,再申请任务控制块的内存。

注意:

        heap_3使用的内存分配方法是直接调用C库malloc()和free()的简单封装,固需要对启动文件里的Heap_Size修改合适的值。

         其他内存分配方法:heap_1、heap_2、heap_4、heap_5所分配的内存,均来自即初始化时分配的全局静态数组ucHeap[],大小由configTOTAL_HEAP_SIZE决定。

1.2 SVC,启动第一个任务

由上可知,SVC与SWI一样,产生一个软中断。

BaseType_t xPortStartScheduler( void )                //启动调度器
{
	portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;      //设置PendSV中断为最低优先级 
	portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;     //设置Systick中断为最低优先级

    vPortSetupTimerInterrupt();                       //使能滴答定时器
    。。。。。。
    prvStartFirstTask();                         
    。。。。。。
}



__asm void prvStartFirstTask( void )        //启动第一个任务       
{
	PRESERVE8

	ldr r0, =0xE000ED08                     //向量表偏移量寄存器的地址
	ldr r0, [r0]                            //将R0保存的地址的值(主堆栈指针MSP初始值)给R0
	ldr r0, [r0]                            //获取主堆栈指针MSP初始值

    msr msp, r0                             //将主堆栈指针MSP初始值赋值给MSP

                                      
	mov r0, #0
	msr control, r0

	cpsie i                                //使能全局中断
	cpsie f
	dsb
	isb

	svc 0                                   //触发SVC软中断
	nop
	nop
}


__asm void vPortSVCHandler( void )          //SVC中断服务函数
{
	PRESERVE8

	/* Get the location of the current TCB. */
	ldr	r3, =pxCurrentTCB                   //R3=最高优先级任务的TCB控制块的地址                          
	ldr r1, [r3]                            //R1=R3保存地址的值
	ldr r0, [r1]                            //R0=R1保存地址的值
	/* Pop the core registers. */
	ldmia r0!, {r4-r11, r14}                //出栈
	msr psp, r0                             //进程栈指针PSP设置为该任务的堆栈
	isb                                     //指令同步
	mov r0, #0                              //R0赋值0
	msr	basepri, r0                         //basepri赋值0,开中断
	bx r14                                  //硬件会自动恢复R0~R3,R12,LR.PC,xPSR寄存器的值
}

二、任务切换

2.1 上下文切换(任务切换)就是触发PendSV中断。切换上下文的方法有这两种:调用系统函数、SysTick中断(相当时间片切换)。

//给中断控制及状态寄存器ICSR的Bit 28写1.即触发PendSV悬起中断
#define portNVIC_INT_CTRL_REG		( * ( ( volatile uint32_t * ) 0xe000ed04 ) )
#define portNVIC_PENDSVSET_BIT		( 1UL << 28UL )

//调用系统函数执行上下文切换

#define taskYIELD()					portYIELD()

#define portYIELD()																
{																				
	portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;		    //触发PendSV中断						

	__dsb( portSY_FULL_READ_WRITE );											
	__isb( portSY_FULL_READ_WRITE );											
}
//SysTick中断服务函数执行上下文切换

void SysTick_Handler (void) 
{

      SysTick->CTRL;                                          //Clear overflow flag
      if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) 
      {
            xPortSysTickHandler();                            //Call tick handler
      }
}


void xPortSysTickHandler( void )
{
	vPortRaiseBASEPRI();
	{
		if( xTaskIncrementTick() != pdFALSE )
		{
			portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;    //触发PendSV悬挂起中断
		}
	}
	vPortClearBASEPRIFromISR();
}

2.2  上面说到,切换任务都是触发PendSV中断,而PendSV中断服务函数原型有必要了解下:

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

	PRESERVE8

	mrs r0, psp                                  //获取当前任务的栈指针(R13)到R0
	isb
	/* Get the location of the current TCB. */
	ldr	r3, =pxCurrentTCB                        //获取当前任务的TCB地址到R3,
	ldr	r2, [r3]                                 //同时将R3的值加载到R2

	/* Is the task using the FPU context?  If so, push high vfp registers. */
	tst r14, #0x10                                //如果使用了FPU,即(r14 & 0x10)是否为1
	it eq                                         //若成立,则需要将高寄存器S16~S31保存到栈
	vstmdbeq r0!, {s16-s31} 

	/* Save the core registers. */
	stmdb r0!, {r4-r11, r14}                      //保存r4~r11,r14寄存器的值

	/* Save the new top of stack into the first member of the TCB. */
	str r0, [r2]                                  //这里是将R0的值写入R2保存的地址里,
                                                  //此时,R0保存当前任务最新的栈顶指针值,
                                                  //而第2步已经将任务控制块的首地址(首地                                    
                                                  //址即任务栈顶地址)写到R2。

	stmdb sp!, {r0, r3}                           //临时保存R0, R3的值,入栈操作
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY //关中断,进入临界区
	msr basepri, r0
	dsb
	isb                                           //等待数据同步
	bl vTaskSwitchContext                         //获取下一个需要运行的任务
	mov r0, #0                                    //这两行是开中断
	msr basepri, r0                               //这两行是开中断,退出临界区
	ldmia sp!, {r0, r3}                           //恢复R0, R3的值,出栈操作
                                                  //注意:
                                                  //调用vTaskSwitchContext后,pxCurrentTCB
                                                  //的值已经被改变,此时R3保存的地址对应的数
                                                  //即pxCurrentTCB已经变成了下一个要运行的    
                                                  //任务的任务控制块。
                                                

	/* The first item in pxCurrentTCB is the task top of stack. */
	ldr r1, [r3]                                  //将pxCurrentTCB的值加载的r1
	ldr r0, [r1]                                  //将r1加载打r0,即新任务堆栈栈顶保存到r0

	/* Pop the core registers. */
	ldmia r0!, {r4-r11, r14}

	/* Is the task using the FPU context?  If so, pop the high vfp registers
	too. */
	tst r14, #0x10                                 
	it eq
	vldmiaeq r0!, {s16-s31}                        //再次判断是否使用FPU,如果是恢复s16~s31

	msr psp, r0                                    //
	isb                                            //这两条更新进程堆栈指针
	#ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata */
		#if WORKAROUND_PMU_CM001 == 1
			push { r14 }
			pop { pc }
			nop
		#endif
	#endif

	bx r14                                          //硬件自动恢复寄存器 R0~R3、R12、LR、PC 
                                                    //和 xPSR 的值
}

三、切换上下文时选择哪个任务优先执行

3.1  查找下一个最高优先级运行的任务

 #define configUSE_PORT_OPTIMISED_TASK_SELECTION  0

该定义为1,则使用硬件的方法查找下一个需要运行的任务,否则使用通用方法。

/*-----------------------------------------------------------*/

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();                    //检查堆栈溢出

		/* Before the currently running task is switched out, save its errno. */
		#if( configUSE_POSIX_ERRNO == 1 )
		{
			pxCurrentTCB->iTaskErrno = FreeRTOS_errno;
		}
		#endif

		/* Select a new task to run using either the generic C or port
		optimised asm code. */
		taskSELECT_HIGHEST_PRIORITY_TASK();                //获取下一个需要运行的任务
		traceTASK_SWITCHED_IN();

		/* After the new task is switched in, update the global errno. */
		#if( configUSE_POSIX_ERRNO == 1 )
		{
			FreeRTOS_errno = pxCurrentTCB->iTaskErrno;
		}
		#endif

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

3.2  通用查找方法

#if ( configUSE_PORT_OPTIMISED_TASK_SELECTION == 0 )

#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;         
}

//pxReadyTasksLists

3.3  硬件查找方法

#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 ] ) );       
} 

版权声明:本文为CSDN博主「小书包VIP」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xqw19891201/article/details/122575294

生成海报
点赞 0

小书包VIP

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

基于STM32的室内环境监测系统

设计简介: 本设计是基于单片机的室内环境监测器,主要实现以下功能: 可实现LCD1602显示环境数据实时监测一氧化碳、甲烷、烟雾值空气质量大于各自限值报警,并通过TTS报警 标签&#x

基于stm32f407的示波器

一.设计要求 二.整体思路 硬件部分主要负责电压的缩放以及垂直灵敏度的控制,因为stm32的大部分引脚最高输入电压为3.3v,而要求的电压需要50v,需要进行电压缩放。 软件部分主要负责方波的实现&#x

实验一 stm32F407VETx点亮流水灯

二、设计指标 使电路板上的8个LED轮流点亮,并按键控制点亮速度。 三、操作 1、CubeMX操作 1.1依据开发板LED引脚设置CubeMX中8个LED的引脚为GPIO_Output模式, 2、按键设置