在FreeRTOS中,數字優先級越小,邏輯優先級也越小。
一、如何支持多優先級
就緒列表 pxReadyTasksLists[ configMAX_PRIORITIES ]是一個數組,數組裏面存儲的是就緒任務的TCB(準確來說是TCB裏面的xStateListItem節點),數組的下表對應任務的優先級,優先級越低對應數組的下標越小。空閒任務的優先級最低,對應的是下標爲0的鏈表。下圖展示了就緒列表中有兩個就續任務,優先級分別是1和2,其中空閒任務沒有被畫出來,空閒任務自系統啓動就會一直就緒,因爲系統至少要保證有一個任務可以運行。
任務在創建時,會根據任務的優先級將任務插入到就緒列表中的不同位置。相同優先級的任務插入到就緒列表中的同一條鏈表中。
pxCurrentTCB是一個全局的TCB指針,用於指向優先級最高的就續任務的TCB,即當前正在運行的TCB。那麼我們想要讓任務支持多優先級,即只要解決在任務切換的時候,讓pxCurrentTCB指向最高優先級的就續任務的TCB就可以了。關於如何找到最高優先級的就續任務的TCB。FreeRTOS提供了兩套方法,一是通用的,另一個是根據特定的處理器優化過的。
二、查找最高優先級的就緒任務代碼
/* 查找最高優先級的就緒任務:通用方法 */
#if ( configUSE_PORT_OPTIMISED_TASK_SELECTION == 0 ) (1)
/* uxTopReadyPriority 存的是就緒任務的最高優先級 */
#define taskRECORD_READY_PRIORITY( uxPriority )\ (2)
{\
if( ( uxPriority ) > uxTopReadyPriority )\
{\
uxTopReadyPriority = ( uxPriority );\
}\
} /* taskRECORD_READY_PRIORITY */
/*-----------------------------------------------------------*/
#define taskSELECT_HIGHEST_PRIORITY_TASK()\ (3)
{\
UBaseType_t uxTopPriority = uxTopReadyPriority;\ (3)-①
/* 尋找包含就緒任務的最高優先級的隊列 */\ (3)-②
while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) )\
{\
--uxTopPriority;\
}\
/* 獲取優先級最高的就緒任務的 TCB,然後更新到 pxCurrentTCB */\
listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, &(pxReadyTasksLists[ uxTopPriority ]));\ (3)-③
/* 更新 uxTopReadyPriority */\
uxTopReadyPriority = uxTopPriority;\ (3)-④
} /* taskSELECT_HIGHEST_PRIORITY_TASK */
/*-----------------------------------------------------------*/
/* 這兩個宏定義只有在選擇優化方法時才用,這裏定義爲空 */
#define taskRESET_READY_PRIORITY( uxPriority )
#define portRESET_READY_PRIORITY( uxPriority, uxTopReadyPriority )
/* 查找最高優先級的就緒任務:根據處理器架構優化後的方法 */
#else /* configUSE_PORT_OPTIMISED_TASK_SELECTION */ (4)
#define taskRECORD_READY_PRIORITY( uxPriority ) \ (5)
portRECORD_READY_PRIORITY( uxPriority, uxTopReadyPriority )
/*-----------------------------------------------------------*/
#define taskSELECT_HIGHEST_PRIORITY_TASK()\ (7)
{\
UBaseType_t uxTopPriority;\
/* 尋找最高優先級 */\
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );\ (7)-①
/* 獲取優先級最高的就緒任務的 TCB,然後更新到 pxCurrentTCB */\
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );\ (7)-②
} /* taskSELECT_HIGHEST_PRIORITY_TASK() */
/*-----------------------------------------------------------*/
#if 0
#define taskRESET_READY_PRIORITY( uxPriority )\ (注意)
{\
if(listCURRENT_LIST_LENGTH(&(pxReadyTasksLists[( uxPriority)]))==(UBaseType_t)0)\
{\
portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) );\
}\
}
#else
#define taskRESET_READY_PRIORITY( uxPriority )\ (6)
{\
portRESET_READY_PRIORITY((uxPriority ), (uxTopReadyPriority));\
}
#endif
#endif /* configUSE_PORT_OPTIMISED_TASK_SELECTION */
(1):查 找 最 高 優 先 級 的 就 緒 任 務 有 兩 種 方 法 , 具 體 由
configUSE_PORT_OPTIMISED_TASK_SELECTION 這個宏控制,定義0選擇通用方法,定義1選擇根據處理器優化的方法。
1、通用方法
(2):taskRECORD_READY_PRIORITY()用於更新uxTopReadyPriority的值。 uxTopReadyPriority 是一個在 task.c 中定義的靜態變量, 用於表示創建的任務的最高優先級, 默認初始化爲 0,即空閒任務的優先級。
uxTopReadyPriority 定義:
#define tskIDLE_PRIORITY ( ( UBaseType_t ) 0U )
/* 定義 uxTopReadyPriority,在 task.c 中定義 */
static volatile UBaseType_t uxTopReadyPriority = tskIDLE_PRIORITY;
(3):taskSELECT_HIGHEST_PRIORITY_TASK()用於尋找優先級最高的就緒任務, 實質就是更新 uxTopReadyPriority和pxCurrentTCB 的值。
(3)-①:將 uxTopReadyPriority 的值暫存到局部變量 uxTopPriority, 接下來需要用到
(3)-②:從最高優先級對應的就緒列表數組下表開始尋找當前鏈表下是否有任務存在,如果沒有,則uxTopPriority 減一操作,繼續尋找下一個優先級對應的鏈表中是否有任務存在,如果有則跳出while循環,表示找到了最高優先級的就緒任務。之所以採用從最高優先級往下搜索,是因爲任務的優先級與就緒列表的下標是一一對應的,優先級越高,對應的就緒列表數組的下表就越大。
(3)-③:獲取優先級最高的就緒任務的 TCB,然後更新到pxCurrentTCB。
(3)-④:更新 uxTopPriority 的值到 uxTopReadyPriority。
2、優化方法
(4):優化的方法得益於Contex-M內核有一個計算前導零的指令CLZ,所謂前導零就是計算一個變量(Cortex-M 內核單片機的變量爲 32 位)從高位開始出現1的位的前面的零的個數,比如:一個32位的變量uxTopReadyPriority,其位0、24、25均置1,其餘位爲0,具體見下圖。那麼使用前導零指令_CLZ(uxTopReadyPriority)可以很快計算出uxTopReadyPriority的前導零的個數爲6.
如果uxTopReadyPriority的每個位號對應的是任務的優先級,任務就緒時,則將對應的位置1,反之則清0。那麼上圖表示優先級0、優先級24、優先級25這三個任務就緒,其中優先級爲25的任務優先級最高。利用前導零指令可以很快計算出就續任務中的最高優先級:( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) ) = ( 31UL - ( uint32_t )6 ) = 25。
(5):taskRECORD_READY_PRIORITY()用於根據傳進來的形參(通常形參就是任務先級) 將變量 uxTopReadyPriority 的某個位置 1。與通用方法中用來表示創建的任務的最高優先級不一樣,它在優化方法中擔任的是一個優先級位圖表的角色,即該變量的每個位對應任務的優先級,如果任務就緒,則將對應的位置 1,反之清零。根據這個原理,只需要計算出 uxTopReadyPriority 的前導零個數就算找到了就緒任務的最高優先級。與通用方法中用來表示創建的任務的最高優先級不一樣,它在優化方法中擔任的是一個優先級位圖表的角色,即該變量的每個位對應任務的優先級,如果任務就緒,則將對應的位置 1,反之清零。根據這個原理,只需要計算出 uxTopReadyPriority 的前導零個數就算找到了就緒任務的最高優先級。
#define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) |= ( 1UL << ( uxPriority ) )
#define portRESET_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) &= ~( 1UL << ( uxPriority ) )
(6):taskRESET_READY_PRIORITY()用於根據傳進來的形參(通常形參就是任務的優先級) 將變量 uxTopReadyPriority 的某個位清零。
(注意):實際上根據優先級調用 taskRESET_READY_PRIORITY()函數復位uxTopReadyPriority時, 要先確保就緒列表中對應優先級下的鏈表沒有任務才行。但是我們當前實現的阻塞延時方案還是通過掃描就緒列表中的TCB的延時變量xTicksToDelay來實現的,還沒有單獨實現延時列表,所以任務非就緒時暫時不能將任務優先級變量從就緒列表中刪除,而是僅僅通過將uxTopReadyPriority中對應的位清零。
(7):taskSELECT_HIGHEST_PRIORITY_TASK()用於尋找優先級最高的就緒任務, 實質就是更新 uxTopReadyPriority和pxCurrentTCB 的值。
(7)-①:根據 uxTopReadyPriority 的值, 找到最高優先級, 然後更新到
uxTopPriority 這個局部變量中。
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )
(7)-②:根據 uxTopPriority 的值, 從就緒列表中找到就緒的最高優先級的任務的 TCB,然後將 TCB 更新到 pxCurrentTCB。
參考:[野火®]《FreeRTOS 內核實現與應用開發實戰—基於STM32》