freertos源码解析-4调度器和任务切换

调度器

调度器就是使用相关的调度算法来选择任务,并安全切换任务运行的代码。基本功能:(1)调度器可以区分就绪态任务和挂起任务;(2)调度器可以选择就绪态中的一个任务,然后激活它;(3)不同调度器之间最大的区别就是如何分配就绪态任务间的完成时间。

  • 抢占式调度器:(1)每个任务都有不同的优先级;(2)任务一直运行到被更高优先级任务抢占或者遇到阻塞式API函数。【单纯的抢占式调度没有时间片的概念,最高优先级的任务就绪,立马抢占,不用等到下一个滴答时间中断来临】
  • 时间片调度:(1)每个任务的优先级都相同,任务会运行固定的时间片个数或者遇到阻塞式API函数。

freertos 的调度

freertos支持抢占式和时间片同时使用。当优先级高的任务进入就绪状态时,高优先级的任务抢占低优先级的任务;当当前运行任务的优先级下,存在多个优先级相同的任务时,所有任务轮流执行,在systick中计时,并触发任务切换。 也就是,如果configUSE_TIME_SLICING设置为0,则RTOS调度程序仍将运行处于就绪状态的最高优先级任务,但不会因为发生滴答中断而在相同优先级的任务之间切换。

freertos 调度触发时机

  • 系统调用,任务使用了 vTaskDelay()或者其他会引起阻塞的系统调用,就会导致任务阻塞,触发任务切换。即任务使用了可能导致任务阻塞的API时,可能触发调度
  • systick中断,当任务使用了时间片调度(即存在多个任务的优先级相同),在xTaskIncrementTick()函数中会检测是否有相同优先级的任务需要运行;即tick中断里面,如果使用了时间片调度,则会触发调度,否则不会
  • ** xTaskResumeAll()**,在此函数中,会将xPendingReadyList列表中的挂起就绪任务转移到pxReadyTasksLists(),因此在xTaskResumeAll()函数中,如果存在转移的任务优先级比当前任务优先级高,则也会产生任务切换。即重新恢复调度器,可能会触发调度

** 以上所有的触发调度,都是使用设置中断控制和状态寄存器的PendSV中断设置位来实现的**

freertos调度过程

所有调度(任务切换)都是通过触发PendSV异常中断来实现的。
在这里插入图片描述

xPortPendSVHandler:
	mrs r0, psp
	isb
	ldr	r3, =pxCurrentTCB			/* Get the location of the current TCB. */
	ldr	r2, [r3]

	stmdb r0!, {r4-r11}				/* Save the remaining registers. */
	str r0, [r2]					/* Save the new top of stack into the first member of the TCB. */

	stmdb sp!, {r3, r14}
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
	msr basepri, r0
	dsb
	isb
	bl vTaskSwitchContext
	mov r0, #0
	msr basepri, r0
	ldmia sp!, {r3, r14}

	ldr r1, [r3]
	ldr r0, [r1]					/* The first item in pxCurrentTCB is the task top of stack. */
	ldmia r0!, {r4-r11}				/* Pop the registers. */
	msr psp, r0
	isb
	bx r14

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

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(); /*lint !e9079 void * is used as this macro is used with timers and co-routines too.  Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
		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 */
	}
}

参考资料

《Mastering the FreeRTOS™ Real Time Kernel》
《cortex M3 权威指南》
《freertos 开发手册》
freertos

公众号:嵌入式软件和硬件
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章