1. 程式人生 > 實用技巧 >5.FreeRTOS任務切換的簡易分析

5.FreeRTOS任務切換的簡易分析

FreeRTOS任務切換的簡易分析

  • 前言:之前分析了建立任務、啟動排程器,在做完這些工作後,就是該完成所有RTOS的最核心的部分,任務的上下文切換,可以說,任務切換就是RTOS。

在分析之前,先分析Cortex-M3的SVC和PendSV

1.SVC

​ 首先要有一個概念,特權模式和使用者模式。在使用者模式下,有的暫存器你是操作不了的,需要進入到特權模式下,才能操作。svc其實就是一個系統呼叫,可以在使用者模式下呼叫,呼叫後就會進入到SVC的異常服務例程裡,這時,CPU就已經進入到了特權模式了,這種機制很好的將核心區域和使用者區域區分開來。

2.PendSV

​ 要引用這個異常之前,要知道以前的OS,是使用SYSTICK來進行任務切換的:

​ 當發生外部中斷的時候,正準備處理,此時SYSTICK異常也觸發了,那麼由於SYSTICK的優先順序大於IRQ的,所以會先進行上下文切換,再執行中斷,很顯然,中斷被延遲執行了,這個是不符合RTOS的實時要求的。

​ 這種方式被否決了,為了解決這個問題,早期的OS會檢測中斷是否活躍,當沒有任何中斷需要響應時才切換上下文:

那麼會有一個問題,當IRQ執行完才會切換任務,那麼切換任務會被拖延。

這時候就引用了新的異常PendSV

​ 可以看到,當要發生一次任務切換時,並不是馬上去切換,而且先掛起一個PendSV異常,假如此時來了一箇中斷,那麼就先執行中斷,執行中斷後,返回使用者模式再響應PendSV異常,PendSV響應服務程式裡面就會去切換任務。

以上都是參考《Cortex-M3權威指南》

3.任務切換

​ 根據上面異常兩個的分析,可以知道FreeRTOS任務切換的位置就是在PendSV裡面。

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

	PRESERVE8

	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 and the critical nesting count. */
	msr psp, r0
	isb
	bx r14
	nop
}

​ 首先,進入到PendSV的時候,CPU是處於特權模式下的,也就是說當前的SP用的是MSP,還要注意的是此時pxTopOfStack和PSP的值是相同的。把psp的值賦值到R0,再把pxCurrentTCB->pxTopOfStack的值賦值到R2,然後手動把R4R11往中PSP中入棧,入棧完了以後,把R0的值更新到pxTopOfStack(R2)裡,然後把R3和R14的值存入MSP中,儲存的目的,是因為等會呼叫了`vTaskSwitchContext`後R3和R14會被修改,R3當前pxCurrentTCB。開啟臨界區,執行``vTaskSwitchContext``,獲取到新的pxCurrentTCB(R1)的地址後,退出臨界區,把R3和R14恢復,然後把R1(pxCurrentTCB)的值放pxCurrentTCB中,那麼pxCurrentTCB就指向新任務的TCB了。取新任務的pxTopOfStack,手動出棧R4R11,然後把R0(出棧後的指標)賦值到PSP中,然後跳轉到新任務中。這就是整個切換任務的過程。

​ 總結:

1. 獲取PSP指標
2. 將當前任務的現場(R4~R11)儲存,儲存現場後,更新`pxTopOfStack`的地址。
3. R3和R14的值存入MSP中
4. 進入臨界區
5. 呼叫`vTaskSwitchContext`
6. 退出臨界區
7. 恢復R3和R14
8. 把`pxCurrentTCB`的值修改為R1(新任務的TCB指標)
9. 手動出棧R4~R11
10. 把R0賦值給PSP
11. 跳轉到新任務執行程式碼

4.任務優先順序選擇

細看vTaskSwitchContext這個函式

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

	#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() */
  1. 首先通過portGET_HIGHEST_PRIORITY獲取到當前哪個優先順序連結串列上有任務處於就緒狀態

具體如下:

#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )

​ CLZ是Cortex特有的指令,是用於計算從高位到低位,連續0的個數,比如:0001 0000 0000 0000 0001 0000 0000 0000 ,結果是3,那麼31-3 = 28,優先順序28連結串列上有就緒任務等待執行。

​ 2.獲取到某個優先順序連結串列後,通過listGET_OWNER_OF_NEXT_ENTRY獲取這個優先順序連結串列上的哪個任務等待執行

#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )										\
{																							\
List_t * const pxConstList = ( pxList );													\
	/* Increment the index to the next item and return the item, ensuring */				\
	/* we don't return the marker used at the end of the list.  */							\
	( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;							\
	if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )	\
	{																						\
		( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;						\
	}																						\
	( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;											\
}

​ 賦值該優先順序連結串列上第一個插入的任務項,並獲取TCB。