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

公衆號:嵌入式軟件和硬件
在這裏插入圖片描述

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