FreeRTOS任務切換的簡易分析
- 架構:Cortex-M3
- 版本:FreeRTOS V9.0.0
- 前言:之前分析了創建任務、啟動調度器,在做完這些工作后,就是該完成所有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異常,PendSV響應服務程序里面就會去切換任務。假如此時來了一個中斷異常,那么就先執行中斷服務例程,執行中斷期間優先級更高的SysTick觸發異常,就會先執行Systick異常服務例程,例程里面會掛起一個PendSV異常,然后退出Systick並進入到剛剛被打斷的中斷服務,中斷服務完成后,就會去響應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,然后手動把R4-R11往中PSP中入棧,入棧完了以后,把R0的值更新到pxTopOfStack(R2)里,然后把R3和R14的值存入MSP中,保存的目的,是因為等會調用了vTaskSwitchContext后R3和R14會被修改,R3當前pxCurrentTCB。開啟臨界區,執行vTaskSwitchContext,獲取到新的pxCurrentTCB(R1)的地址后,退出臨界區,把R3和R14恢復,然后把R1(pxCurrentTCB)的值放pxCurrentTCB中,那么pxCurrentTCB就指向新任務的TCB了。取新任務的pxTopOfStack,手動出棧R4~R11,然后把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() */
- 首先通過
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; \
}
假設有兩個任務,任務A和任務B,運行兩次listGET_OWNER_OF_NEXT_ENTRY

