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() */
- 首先通過
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。