? ? ? ? 在FreeRTOS中,數字優先級越小,邏輯優先級也越小;在任務創建時,會根據任務的優先級將任務插入就緒列表不同的位置。
????????List_t pxReadyTasksLists[ configMAX_PRIORITIES ] 就緒列表是一個數組,數組中存儲的是就緒任務TCB(任務控制塊通過自己的鉤子<TCB中的xStateListItem節點>掛上了List_t)。
? ? ? ? 創建任務時,a. 會根據任務的優先級將任務插入就緒列表數組的不同的位置;
? ? ? ?b.? 相同優先級的任務插入就緒列表數組中的同一條鏈表中。
? ? ? ? 要想任務支持優先級,即只要實現在任務切換(taskYIELD)時,讓pxCurrentTCB指向最高優先級的就緒任務的TCB既可以。
重要數據
0.? 全局TCB指針
/* 當前正在運行的任務的任務控制塊指針,默認初始化為NULL */
TCB_t * volatile pxCurrentTCB = NULL;
要想讓任務支持優先級,即只要實現任務切換(portYIELD)時,讓pxCurrentTCB指向最高優先級的就緒任務的TCB即可。
1. 靜態變量:uxTopReadyPriority
位置:在task.c文件中定義;靜態變量;
static volatile UBaseType_t uxTopReadyPriority ?? ??? ?= tskIDLE_PRIORITY;
表示創建的任務的最高優先級,默認初始化為0,即空閑任務的優先級。
數值越大,優先級越高;
2. 修改任務控制塊:增加一變量
? ? ? ? 增加了一個變量:UBaseType_t?? ??? ??? ?uxPriority;
描述任務塊的優先級。
typedef struct tskTaskControlBlock
{volatile StackType_t *pxTopOfStack; /* 棧頂 */ListItem_t xStateListItem; /* 任務節點 */StackType_t *pxStack; /* 任務棧起始地址 *//* 任務名稱,字符串形式 */char pcTaskName[ configMAX_TASK_NAME_LEN ];TickType_t xTicksToDelay;UBaseType_t uxPriority;
} tskTCB;
typedef tskTCB TCB_t;
3. 全局任務數
static volatile UBaseType_t uxCurrentNumberOfTasks ?? ?= ( UBaseType_t ) 0U;
static UBaseType_t uxTaskNumber ?? ??? ??? ??? ??? ?= ( UBaseType_t ) 0U;
此處有一個疑問:這個變量做什么用?
? ? ? ? 查找當前最高優先級的就緒任務,給uxTopReadyPriority 賦值,給pxCurrentTCB賦值,有兩種方法。
兩種方法
? ? ? ? 查找最高優先級的就緒任務有兩種方法,具體由宏configUSE_PORT_OPTIMISED_TASK_SELECTION控制,定義為0選擇通用方法,定義為1選擇根據處理器優化的方法,該宏默認在portmacro.h文件中定義為1,即使用優化過的方法。
#ifndef configUSE_PORT_OPTIMISED_TASK_SELECTION#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
#endif
? ? ? 該功能主要是通過四個宏來實現:
????????taskRECORD_READY_PRIORITY( uxPriority )? ?:記錄就緒最高優先級的就緒任務uxTopReadyPriority 賦值;
????????taskSELECT_HIGHEST_PRIORITY_TASK():在任務列表中尋找就緒最高優先級的就緒任務,獲取優先級最高的就緒任務的TCB,然后更新到pxCurrentTCB;
????????taskRESET_READY_PRIORITY( uxPriority )
????????portRESET_READY_PRIORITY( uxPriority, uxTopReadyPriority )
通用方法
? ? ? ? 在通用方法中實現了上面的宏1、宏2;宏3~4為空。
兩個宏定義
taskRECORD_READY_PRIORITY( uxPriority )? ?
位置:在task.c文件中定義;
實現記錄當前運行任務的優先級,直接得到優先級的序號,并更新到靜態變量 uxTopReadyPriority 中;
#define taskRECORD_READY_PRIORITY( uxPriority ) \{ \if( ( uxPriority ) > uxTopReadyPriority ) \{ \uxTopReadyPriority = ( uxPriority ); \} \} /* taskRECORD_READY_PRIORITY */
taskSELECT_HIGHEST_PRIORITY_TASK()
位置:在task.c文件中定義;
#define taskSELECT_HIGHEST_PRIORITY_TASK() \{ \UBaseType_t uxTopPriority = uxTopReadyPriority; \\/* 尋找包含就緒任務的最高優先級的隊列 */ \while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) \{ \--uxTopPriority; \} \\/* 獲取優先級最高的就緒任務的TCB,然后更新到pxCurrentTCB */ \listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \/* 更新uxTopReadyPriority */ \uxTopReadyPriority = uxTopPriority; \} /* taskSELECT_HIGHEST_PRIORITY_TASK */
1)用于尋找優先級最高的就緒任務、更新靜態變量 uxTopReadyPriority;調用宏:listLIST_IS_EMPTY( pxList )
/* 判斷鏈表是否為空 */
#define listLIST_IS_EMPTY( pxList ) ( ( BaseType_t ) ( ( pxList )->uxNumberOfItems == ( UBaseType_t ) 0 ) )
此處有一個疑問:鏈表中的變量計數器(uxNumberOfItems)用來做什么用?
/* 鏈表結構體定義 */
typedef struct xLIST
{
?? ?UBaseType_t uxNumberOfItems; ? ?/* 鏈表節點計數器 */
?? ?ListItem_t * ?pxIndex;?? ??? ??? ?/* 鏈表節點索引指針 */
?? ?MiniListItem_t xListEnd;?? ??? ?/* 鏈表最后一個節點 */
} List_t;
?
現在可以回答這個問題了,也就是這列鏈表中節點的個數,當任務的優先級一樣的話,是插到就緒任務鏈表數組的同一位置的,所以同一優先級就緒任務需要計數:uxNumberOfItems;
a. 將節點從鏈表中刪除:
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove ) 函數會減1;
b. 將節點插入到鏈表的尾部 :void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem ) 函數中加1;
2)更新全局變量pxCurrentTCB;調用宏:listGET_OWNER_OF_NEXT_ENTRY(..)
/* 獲取鏈表節點的OWNER,即TCB */
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \
{ \List_t * const pxConstList = ( pxList ); \/* 節點索引指向鏈表第一個節點調整節點索引指針,指向下一個節點,如果當前鏈表有N個節點,當第N次調用該函數時,pxInedex則指向第N個節點 */\( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \/* 當前鏈表為空 */ \if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \{ \( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \} \/* 獲取節點的OWNER,即TCB */ \( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \
}
/* 節點結構體定義 */
struct xLIST_ITEM
{
?? ?TickType_t xItemValue; ? ? ? ? ? ? /* 輔助值,用于幫助節點做順序排列 */?? ??? ??? ?
?? ?struct xLIST_ITEM * ?pxNext; ? ? ? /* 指向鏈表下一個節點 */?? ??? ?
?? ?struct xLIST_ITEM * ?pxPrevious; ? /* 指向鏈表前一個節點 */?? ?
?? ?void * pvOwner;?? ??? ??? ??? ??? ? ? /* 指向擁有該節點的內核對象,通常是TCB */
?? ?void * ?pvContainer;?? ??? ? ? ? ? /* 指向該節點所在的鏈表 */
};
typedef struct xLIST_ITEM ListItem_t; ?/* 節點數據類型重定義 */
3)繼續更新靜態變量 uxTopReadyPriority;
優化方法
? ? ? ? Cortex-M 內核有一個計算前導零的指令:CLZ。用于計算一個變量從高位開始第一次出現1的位前面的0的個數。
????????__clz( ( uxReadyPriorities ):
a.?uxReadyPriorities 為32位,每一個位號對應一個任務的優先級,1就緒,反之為0;
b.?32個優先級的任務:?( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )表示最高就緒任務的等級;
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )
兩個宏定義
taskRECORD_READY_PRIORITY( uxPriority )
位置:在task.c文件中定義;
#define taskRECORD_READY_PRIORITY( uxPriority ) portRECORD_READY_PRIORITY( uxPriority, uxTopReadyPriority )
里頭又涉及到兩個宏:portRECORD_READY_PRIORITY? ?和 portRESET_READY_PRIORITY
位置:在portmacro.h文件中定義;
a.?portRECORD_READY_PRIORITY 完成功能:任務就緒就把uxReadyPriorities相應的位置1;得到能夠反應就緒的32位的變量uxReadyPriorities;
b.portRESET_READY_PRIORITY 功能相反;
#if configUSE_PORT_OPTIMISED_TASK_SELECTION == 1/* 根據優先級設置/清除優先級位圖中相應的位 */#define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) |= ( 1UL << ( uxPriority ) )#define portRESET_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) &= ~( 1UL << ( uxPriority ) )/*-----------------------------------------------------------*/#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )#endif /* taskRECORD_READY_PRIORITY */
taskSELECT_HIGHEST_PRIORITY_TASK()
位置:在portmacro.h文件中定義;
就是利用前面提到的計算前導零的指令:CLZ 實現,算出就緒最高優先級任務的的級別出來;
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )
從而實現宏:taskSELECT_HIGHEST_PRIORITY_TASK。
#define taskSELECT_HIGHEST_PRIORITY_TASK() \{ \UBaseType_t uxTopPriority; \\/* 尋找最高優先級 */ \portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \/* 獲取優先級最高的就緒任務的TCB,然后更新到pxCurrentTCB */ \listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \} /* taskSELECT_HIGHEST_PRIORITY_TASK() */
有關任務操作重要函數
1. 修改:TaskHandle_t xTaskCreateStatic(...)
1)內部增加了將任務添加到就緒列表的功能:prvAddNewTaskToReadyList;
2)在原本的prvInitialiseNewTask中增加了設置優先級的功能;
prvInitialiseNewTask( pxTaskCode, pcName, ulStackDepth, pvParameters,uxPriority, &xReturn, pxNewTCB);
原來的函數,初始化與任務相關的列表,如就緒列表;
static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
是一個帶參宏,位置:在task.c文件中定義;
static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{/* 進入臨界段 */taskENTER_CRITICAL();{/* 全局任務計時器加一操作 */uxCurrentNumberOfTasks++;/* 如果pxCurrentTCB為空,則將pxCurrentTCB指向新創建的任務 */if( pxCurrentTCB == NULL ){pxCurrentTCB = pxNewTCB;/* 如果是第一次創建任務,則需要初始化任務相關的列表 */if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ){/* 初始化任務相關的列表 */prvInitialiseTaskLists();}}else /* 如果pxCurrentTCB不為空,則根據任務的優先級將pxCurrentTCB指向最高優先級任務的TCB */{if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority ){pxCurrentTCB = pxNewTCB;}}uxTaskNumber++;/* 將任務添加到就緒列表 */prvAddTaskToReadyList( pxNewTCB );}/* 退出臨界段 */taskEXIT_CRITICAL();
}
完成如下功能:
a. 初始化任務鏈表(若是第一次):prvInitialiseTaskLists;
b. 把任務加到就緒列表:prvAddTaskToReadyList;
?這個是通過前面的宏 (查找優先級)+ 列表插入函數實現(根據優先級將任務插入就緒列表pxReadyTasksLists[])
prvAddTaskToReadyList( pxNewTCB );
* 將任務添加到就緒列表 */ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
#define prvAddTaskToReadyList( pxTCB )?? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ? ? \
?? ?taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority );?? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ??? ? ? \
?? ?vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) );?
完成
a.?任務控制塊的鉤子(過自己的鉤子<TCB中的xStateListItem節點>掛上了List_t)。);
b. 任務控制塊的容納者是鏈表數組的某一個;
c.該鏈表擁有的任務(控制塊)的數量加一;
2. vTaskStartScheduler()維持不變
完成如下功能:
1)創建空閑任務;
2)啟動任務調度器xPortStartScheduler;
3.修改:void vTaskDelay( const TickType_t xTicksToDelay )
位置:在task.c文件中定義;
void vTaskDelay( const TickType_t xTicksToDelay )
{TCB_t *pxTCB = NULL;/* 獲取當前任務的TCB */pxTCB = pxCurrentTCB;/* 設置延時時間 */pxTCB->xTicksToDelay = xTicksToDelay;/* 將任務從就緒列表移除 *///uxListRemove( &( pxTCB->xStateListItem ) );taskRESET_READY_PRIORITY( pxTCB->uxPriority );/* 任務切換 */taskYIELD();
}
此處的修改注意,以前是從任務列表中移除:uxListRemove( &( pxTCB->xStateListItem ) );
現在是通過函數(宏):taskRESET_READY_PRIORITY( pxTCB->uxPriority ) ;實現復位變量uxReadyPriorities相對于的位;
然后是調用任務切換taskYIELD,觸發中斷:vTaskSwitchContext;
4. 修改:void vTaskSwitchContext( void )
任務切換,即尋找優先級最高的就緒任務;
利用宏自動尋找優先級最高的就緒任務的TCB,然后更新到全局變量任務控制塊指針pxCurrentTCB中;
/* 任務切換,即尋找優先級最高的就緒任務 */
void vTaskSwitchContext( void )
{/* 獲取優先級最高的就緒任務的TCB,然后更新到pxCurrentTCB */taskSELECT_HIGHEST_PRIORITY_TASK();
}
5. 修改:void xTaskIncrementTick( void )
位置:在task.c文件中定義;
由定時中斷xPortSysTickHandler調用;完成如下功能:
a. 實現全局變量xTickCount
?計數操作;
此處有一個疑問:xTickCount
?的加1為何這么麻煩,通過xConstTickCount常量來實現啊?
? ? const TickType_t xConstTickCount = xTickCount + 1;
? ? xTickCount = xConstTickCount;
可能的原因 這種寫法可能是出于以下原因之一: 1. 調試方便:使用局部變量 xConstTickCount 可以方便地在調試時觀察 xTickCount 自增后的值。 2. 代碼風格:某些開發者可能傾向于通過局部變量來分離計算和賦值邏輯,以提高代碼的可讀性。 3. 歷史遺留:這段代碼可能是從更復雜的邏輯簡化而來的,原本 xConstTickCount 可能有其他用途,但后來簡化成了當前的形式。
b. 掃描所有的就緒列表的時間是否到了,到了記錄:taskRECORD_READY_PRIORITY( pxTCB->uxPriority );
c. 掃描結束進行任務切換portYIELD,觸發中斷;
void xTaskIncrementTick( void )
{TCB_t *pxTCB = NULL;BaseType_t i = 0;/* 更新系統時基計數器xTickCount,xTickCount是一個在port.c中定義的全局變量 */const TickType_t xConstTickCount = xTickCount + 1;xTickCount = xConstTickCount;/* 掃描就緒列表中所有線程的xTicksToDelay,如果不為0,則減1 */for(i=0; i<configMAX_PRIORITIES; i++){pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &pxReadyTasksLists[i] ) );if(pxTCB->xTicksToDelay > 0){pxTCB->xTicksToDelay --;}}/* 任務切換 */portYIELD();
}
6. 任務切換函數?portYIELD() 與taskYIELD
? ? ? ? 功能:設置 PendSV 的中斷掛起位,產生上下文切換。
#define taskYIELD()?? ??? ??? ??? ??? ? ? ? ? portYIELD()
#define portYIELD() \
{ \/* 設置 PendSV 的中斷掛起位,產生上下文切換 */ \portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \\/* Barriers are normally not required but do ensure the code is completely \within the specified behaviour for the architecture. */ \__dsb( portSY_FULL_READ_WRITE ); \__isb( portSY_FULL_READ_WRITE ); \
}