在學習本章之前,爲了實現任務的阻塞延時,在任務控制塊中內置了一個延時變量xTicksToDelay。當每次任務需要延時時,就初始化xTicksToDelay需要延時的時間,然後將任務掛起,這裏所說的掛起只是將任務在優先級位圖表uxTopReadyPriority中對應的位清零,並不會將任務從就緒列表中刪除。當每次時基中斷(SysTick中斷)來臨時,就掃描就緒列表中每個任務的xTicksToDelay,如果xTicksToDelay大於0則遞減一次,然後判斷xTicksToDelay是否爲0,如果爲0則表示延時時間到,將該任務就緒(即將任務在優先級位圖表uxTopReadyPriority中對應的位置位),然後進行任務切換。這種延時的缺點是,在每個時基中斷中需要對所有的任務都掃描一遍,費時,優點是易理解。
一、任務延時列表的工作原理
在FreeRTOS中,有一個任務延時列表(實際有兩個,但作用一樣),當任務需要延時時,先將任務掛起,即先將任務從就緒列表中刪除,然後插入到任務延時列表,同時更新下一個任務的解鎖時刻變量:xNextTaskUnblockTime 的值。
xNextTaskUnblockTime 的值等於系統時基計數器的值xTickCount 加上任務需要延時的值xTicksToDelay。當系統時基計數器xTickCount 的值等於xNextTaskUnblockTime 的值時,就表示有任務延時到期了,需要將該任務就緒。
任務延時列表維護着一條雙向鏈表,每個節點代表了正在延時的任務,節點按照延時時間大小做升序排列。當每次時基中斷來臨時,就拿系統時基計數器的值xTickCount 和下一個任務的解鎖時刻變量xNextTaskUnblockTime 的值比較,如果相等,則表示有任務延時到期,需要將該任務就緒,否則只是單純地更新時基計數器xTickCount 的值,然後進行任務切換。
二、FreeRTOS中實現任務延時列表
1、定義任務延時列表
static List_t xDelayedTaskList1; (1)
static List_t xDelayedTaskList2; (2)
static List_t * volatile pxDelayedTaskList; (3)
static List_t * volatile pxOverflowDelayedTaskList; (4)
(1)(2):定義兩個任務延時列表,當系統時基計數器xTickCount 沒有溢出時,用一條列表,當xTickCount 移除後,用另外一條鏈表。
(3):任務延時列表指針,指向xTickCount 沒有溢出時使用的那條列表
(4):任務延時列表指針,指向xTickCount 溢出時使用的那條列表
2、任務延時列表初始化
任務延時列表屬於任務列表的一種,在prvInitialiseTaskLists()函數中初始化。
/* 初始化任務相關的列表 */
void prvInitialiseTaskLists( void )
{
UBaseType_t uxPriority;
/* 初始化就緒列表 */
for ( uxPriority = ( UBaseType_t ) 0U;
uxPriority < ( UBaseType_t ) configMAX_PRIORITIES;
uxPriority++ )
{
vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
}
vListInitialise( &xDelayedTaskList1 );
vListInitialise( &xDelayedTaskList2 );
pxDelayedTaskList = &xDelayedTaskList1;
pxOverflowDelayedTaskList = &xDelayedTaskList2;
}
3、定義xNextTaskUnblockTime
xNextTaskUnblockTime 是一個在 task.c 中定義的靜態變量,用於表示下一個任務的解鎖時刻。 xNextTaskUnblockTime 的值等於系統時基計數器的值 xTickCount 加上任務需要延時值 xTicksToDelay。當系統時基計數器 xTickCount 的值與 xNextTaskUnblockTime 相等時,就表示有任務延時到期了,需要將該任務就緒。
4、定義xNextTaskUnblockTime
xNextTaskUnblockTime 在 vTaskStartScheduler()函數中初始化爲portMAX_DELAY(portMAX_DELAY 是一個 portmacro.h 中定義的宏,默認爲 0xffffffffUL) 。
void vTaskStartScheduler( void )
{
/*==================創建空閒任務 start=========================*/
TCB_t *pxIdleTaskTCBBuffer = NULL;
StackType_t *pxIdleTaskStackBuffer = NULL;
uint32_t ulIdleTaskStackSize;
/* 獲取空閒任務的內存:任務棧和任務 TCB */
vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer,
&pxIdleTaskStackBuffer,
&ulIdleTaskStackSize );
xIdleTaskHandle =
xTaskCreateStatic( (TaskFunction_t)prvIdleTask,
(char *)"IDLE",
(uint32_t)ulIdleTaskStackSize ,
(void *) NULL,
(UBaseType_t) tskIDLE_PRIORITY,
(StackType_t *)pxIdleTaskStackBuffer,
(TCB_t *)pxIdleTaskTCBBuffer );
/*======================創建空閒任務 end===================*/
xNextTaskUnblockTime = portMAX_DELAY;
xTickCount = ( TickType_t ) 0U;
/* 啓動調度器 */
if ( xPortStartScheduler() != pdFALSE )
{
/* 調度器啓動成功,則不會返回,即不會來到這裏 */
}
}
參考:[野火®]《FreeRTOS 內核實現與應用開發實戰—基於STM32》