4.FreeRTOS排程器的啟動簡易分析
FreeRTOS排程器的啟動簡易分析
- 前言:上一篇我分析了關於一個任務的建立過程,既然建立了任務,自然是要用。那麼FreeRTOS中對於任務的切換,排程器發揮著巨大的作用,這是一個核心。
1.從函式vTaskStartScheduler
入手
便於分析我簡化了程式碼:
void vTaskStartScheduler( void ) { BaseType_t xReturn; /* Add the idle task at the lowest priority. */ #if( configSUPPORT_STATIC_ALLOCATION == 1 ) { .... .... ... } #else { /* The Idle task is being created using dynamically allocated RAM. */ xReturn = xTaskCreate( prvIdleTask, "IDLE", configMINIMAL_STACK_SIZE, ( void * ) NULL, ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), &xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */ } #endif /* configSUPPORT_STATIC_ALLOCATION */ #if ( configUSE_TIMERS == 1 ) { if( xReturn == pdPASS ) { xReturn = xTimerCreateTimerTask(); } else { mtCOVERAGE_TEST_MARKER(); } } #endif /* configUSE_TIMERS */ if( xReturn == pdPASS ) { /* Interrupts are turned off here, to ensure a tick does not occur before or during the call to xPortStartScheduler(). The stacks of the created tasks contain a status word with interrupts switched on so interrupts will automatically get re-enabled when the first task starts to run. */ portDISABLE_INTERRUPTS(); #if ( configUSE_NEWLIB_REENTRANT == 1 ) { /* Switch Newlib's _impure_ptr variable to point to the _reent structure specific to the task that will run first. */ _impure_ptr = &( pxCurrentTCB->xNewLib_reent ); } #endif /* configUSE_NEWLIB_REENTRANT */ xNextTaskUnblockTime = portMAX_DELAY; xSchedulerRunning = pdTRUE; xTickCount = ( TickType_t ) 0U; /* If configGENERATE_RUN_TIME_STATS is defined then the following macro must be defined to configure the timer/counter used to generate the run time counter time base. */ portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(); /* Setting up the timer tick is hardware specific and thus in the portable interface. */ if( xPortStartScheduler() != pdFALSE ) { /* Should not reach here as if the scheduler is running the function will not return. */ } else { /* Should only reach here if a task calls xTaskEndScheduler(). */ } } else { /* This line will only be reached if the kernel could not be started, because there was not enough FreeRTOS heap to create the idle task or the timer task. */ configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY ); } /* Prevent compiler warnings if INCLUDE_xTaskGetIdleTaskHandle is set to 0, meaning xIdleTaskHandle is not used anywhere else. */ ( void ) xIdleTaskHandle; }
- 用動態建立任務的方式,建立了一個空閒任務
- 建立以及初始化定時器任務
- 最後呼叫了
xPortStartScheduler
我簡化了一些程式碼xPortStartScheduler
/* * See header file for description. */ BaseType_t xPortStartScheduler( void ) { #if( configASSERT_DEFINED == 1 ) { ... ... ... } #endif /* conifgASSERT_DEFINED */ /* Make PendSV and SysTick the lowest priority interrupts. */ portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI; portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI; /* Start the timer that generates the tick ISR. Interrupts are disabled here already. */ vPortSetupTimerInterrupt(); /* Initialise the critical nesting count ready for the first task. */ uxCriticalNesting = 0; /* Start the first task. */ prvStartFirstTask(); /* Should not get here! */ return 0; }
首先是我在分析List的時候講過關於SVC和PendSV優先順序的賦值情況,實際上這兩個優先順序是最低的。然後是啟動軟體定時器vPortSetupTimerInterrupt()
,設定臨界區巢狀深度uxCriticalNesting
為0,呼叫prvStartFirstTask()
開啟第一個任務。
函式如下:
__asm void prvStartFirstTask( void ) { PRESERVE8 /* Use the NVIC offset register to locate the stack. */ ldr r0, =0xE000ED08 ldr r0, [r0] ldr r0, [r0] /* Set the msp back to the start of the stack. */ msr msp, r0 /* Globally enable interrupts. */ cpsie i cpsie f dsb isb /* Call SVC to start the first task. */ svc 0 nop nop }
首先,從中斷向量表偏移暫存器0xE000ED08
中取出向量表的偏移地址,向量表的前四個位元組就是主堆疊地址,把地址傳給MSP,開啟總中斷,呼叫svc 0
,觸發ISR。當進入SVC異常服務例程時,CPU處於特權模式了。在特權模式下就允許操作只有特權模式下能操作的硬體。
接下來看SVC異常服務例程:
__asm void vPortSVCHandler( void )
{
PRESERVE8
ldr r3, =pxCurrentTCB /* Restore the context. */
ldr r1, [r3] /* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
ldr r0, [r1] /* The first item in pxCurrentTCB is the task top of stack. */
ldmia r0!, {r4-r11} /* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */
msr psp, r0 /* Restore the task stack pointer. */
isb
mov r0, #0
msr basepri, r0
orr r14, #0xd
bx r14
}
pxCurrentTCB的前四個位元組就是pxTopOfStack
,我前面分析了很久的pxTopOfStack
並沒有浪費,因為我知道,這個指標指向的位置,就是存放任務現場的地址。取出pxTopOfStack
後,按照Cortex-M3的出棧順序,取出棧中各個暫存器的值。最開始,R0存放的是pxTopOfStack
的值,執行手動出棧R4~R11的之後,R0的值比pxTopOfStack
多0x20,然後把R0賦值到PSP,此時執行了bx r14之後,PSP會再次出棧xPSR、PC、LR、R12、R3~R0,PSP又會再增加0x20,也就是到了任務執行程式碼的時候,PSP就會比pxTopOfStack
大0x40。如圖:
最後呼叫:
orr r14, #0xd
bx r14
根據Cortex-M3權威手冊上的說明,R14的bit0為1表示返回thumb狀態,bit1和bit2分別表示返回後sp用msp還是psp、以及返回到特權模式還是使用者模式。R14儲存的是返回的地址,這裡的返回地址一定就是某個任務的函式指標。返回時,但並不是簡簡單單返回,當或上0xd時,就是要讓CPU進入到執行緒模式、Thumb狀態,當執行緒模式時,才會使用PSP。
這麼看來一切都通了:建立任務的時候,把任務當成正在執行的任務,然後把任務現場統統入棧到pxTopOfStack
,在啟動排程器時,又把pxTopOfStack
儲存的現場統統出棧,最後設定R14的低三位,使得CPU進入使用者模式和跳到某個任務的執行程式碼中。