眾所周知,在Cortex-M內核中,系統節拍由Systick時鐘提供,當配置好系統滴答時鐘後,每次時鐘中斷就會觸發中斷處理函數 xPortSysTickHandler(), void xPortSysTickHandler( void ) { /* The SysTick runs at the l ...
眾所周知,在Cortex-M內核中,系統節拍由Systick時鐘提供,當配置好系統滴答時鐘後,每次時鐘中斷就會觸發中斷處理函數 xPortSysTickHandler(),
void xPortSysTickHandler( void ) { /* The SysTick runs at the lowest interrupt priority, so when this interrupt * executes all interrupts must be unmasked. There is therefore no need to * save and then restore the interrupt mask value as its value is already * known - therefore the slightly faster vPortRaiseBASEPRI() function is used * in place of portSET_INTERRUPT_MASK_FROM_ISR(). */ vPortRaiseBASEPRI();//屏蔽歸屬FreeRTOS的中斷優先順序 { /* Increment the RTOS tick. */ if( xTaskIncrementTick() != pdFALSE )//時鐘計數處理 { /* A context switch is required. Context switching is performed in * the PendSV interrupt. Pend the PendSV interrupt. */ portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;//如果需要切換上下文操作,PendSV標記置位 } } vPortClearBASEPRIFromISR(); }
這部分主要是依靠 xTaskIncrementTick(),來判斷任務切換是否在此次系統時鐘中斷時被需要。如果是,則PendSV標記置位,等待觸發PendSV中斷。
來看看 xTaskIncrementTick()。
BaseType_t xTaskIncrementTick( void ) { TCB_t * pxTCB; TickType_t xItemValue; BaseType_t xSwitchRequired = pdFALSE; /* Called by the portable layer each time a tick interrupt occurs. * Increments the tick then checks to see if the new tick value will cause any * tasks to be unblocked. */ traceTASK_INCREMENT_TICK( xTickCount ); if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ) //調度是否被掛起,預設為否 { /* Minor optimisation. The tick count cannot change in this * block. */ const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1; /* Increment the RTOS tick, switching the delayed and overflowed * delayed lists if it wraps to 0. */ xTickCount = xConstTickCount; if( xConstTickCount == ( TickType_t ) 0U ) /*lint !e774 'if' does not always evaluate to false as it is looking for an overflow. 如果xConstTickCount為0,說明溢出了*/ { taskSWITCH_DELAYED_LISTS();/*切換延遲列表*/ } else { mtCOVERAGE_TEST_MARKER(); } /* See if this tick has made a timeout expire. Tasks are stored in * the queue in the order of their wake time - meaning once one task * has been found whose block time has not expired there is no need to * look any further down the list. */ if( xConstTickCount >= xNextTaskUnblockTime ) { for( ; ; ) { if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE ) { /* The delayed list is empty. Set xNextTaskUnblockTime * to the maximum possible value so it is extremely * unlikely that the * if( xTickCount >= xNextTaskUnblockTime ) test will pass * next time through. */ xNextTaskUnblockTime = portMAX_DELAY; /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ break; } else { /* The delayed list is not empty, get the value of the * item at the head of the delayed list. This is the time * at which the task at the head of the delayed list must * be removed from the Blocked state. */ pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); /*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. */ xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) ); ......
關鍵問題是,這個函數使用到了 pxDelayedTaskList, 這定義在本文件
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;
該變數初始化為0,該變數正常初始化位置在創建 Task 對象等的函數中, 也就是說,如果在Tick中斷到來時,如果還沒有任務被創建,就會導致不可預期的結果,中斷服務函數會使用這個野指針,執行任務切換。
這會導致觸發棧溢出鉤子函數,或者是直接 Hardfault。
有些硬體初始化需要藉助delay功能,不得不在初始化之前配置SysTick。而又不希望在硬體初始化階段觸發這個Bug。
所以在配置SysTick之前,先創建一個初始化任務,初始化 pxDelayedTaskList 這個指針,在初始化任務里配置SysTick,和其他初始化,這樣能夠避免此類問題。
或者是在配置SysTick的時候屏蔽中斷,一切準備就緒後,開啟中斷。
執行 vTaskStartScheduler(),預設創建一個空閑任務。