【freertos】004-任務創建與刪除及其實現細節
- 2022 年 3 月 29 日
- 筆記
- /label/FreeRTOS, freeRTOS, 嵌入式, 教程集合
前言
後面都是已動態內存任務為例來分析。
注意:
-
由於當前學習是在linux上跑的freertos,對於freertos底層相關接口,從demo工程來看,都是posix標準相關。
-
鑒於freertos多用於ARM架構,本教程涉及到硬件接口,作者會分兩條路線講解:
- posix標準接口。
- cortex m3/4架構相關接口。
參考:
本文默認按堆棧向下生長方式講解。
4.1 任務控制塊
/* 任務控制塊 */
typedef struct tskTaskControlBlock
{
volatile StackType_t * pxTopOfStack; /* 指向放在任務堆棧上的最後一項的位置。這必須是TCB結構體的第一個成員。 */
#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings; /* MPU設置被定義為端口層的一部分。這必須是TCB結構體的第二個成員。 */
#endif
ListItem_t xStateListItem; /* 任務的狀態列表項引用的列表表示該任務的狀態(就緒、阻塞、掛起)。 */
ListItem_t xEventListItem; /* 用於從事件列表中引用任務 */
UBaseType_t uxPriority; /* 任務優先級 */
StackType_t * pxStack; /* 任務棧其實地址指針 */
char pcTaskName[ configMAX_TASK_NAME_LEN ]; /* 創建時為任務指定的描述性名稱。便於調試。非限定的char類型只允許用於字符串和單個字符。 */
#if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
StackType_t * pxEndOfStack; /* 指向任務棧末 */
#endif
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting; /* 自己維護臨界嵌套深度,不用在端口層維護。 */
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; /* 標記當前任務控制塊序號,由內核決定,每個任務不同。 */
UBaseType_t uxTaskNumber; /* 標記當前任務序號,但不是有內核決定,而是通過API函數`vTaskSetTaskNumber()`來設置的。 */
#endif
#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; /* 基優先級,用於優先級繼承時使用 */
UBaseType_t uxMutexesHeld; /* 當前任務獲取到的互斥量個數 */
#endif
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t pxTaskTag; /* 任務標籤 */
#endif
#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
void * pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ]; /* 本地內存指針數組 */
#endif
#if ( configGENERATE_RUN_TIME_STATS == 1 )
configRUN_TIME_COUNTER_TYPE ulRunTimeCounter; /* 存儲任務處於運行狀態所花費的時間 */
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
/* 「沒有用過」 */ struct _reent xNewLib_reent;
#endif
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ]; /* 任務通知值數組 */
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ]; /* 任務通狀態數組 */
#endif
#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
uint8_t ucStaticallyAllocated; /* 標記任務是動態內存創建還是靜態內存創建 */
#endif
#if ( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted; /* 解除阻塞標記 */
#endif
#if ( configUSE_POSIX_ERRNO == 1 )
int iTaskErrno; /* 當前任務的錯誤碼 */
#endif
} tskTCB;
詳細說明各成員:
pxTopOfStack
:
- 任務棧頂指針,必須放在任務控制塊首位,指向任務當前堆棧的棧頂,且總是指向最後一個壓棧的項目。
- 該值在任務切換時才會更新。
xMPUSettings
:
- 如果使用MPU,
xMPUSettings
必須位於結構體的第二項,用於MPU設置。
xStateListItem
和xEventListItem
:
- 狀態鏈表節點和事件鏈表節點。
- 這些鏈表主要被OS調度器使用,用於跟蹤、處理任務。
- 對於鏈表的學習可以百度搜索下:李柱明 鏈表
uxPriority
:
- 任務優先級,freertos是0為最低優先級。
- 一般在創建任務時配置,也可以動態修改。對於動態修改,後面任務控制章節會講述。
pxStack
:
- 任務棧底指針,指向任務堆棧的起始位置。
- 在任務創建時就被賦值了。
- 棧底指針
pxStack
被賦值後就不會改變的,而棧頂指針pxTopOfStack
是會隨着出入棧變化的。 - 對於向下生長的棧,該值可用於任務棧溢出監測。在任務棧初始化時,會初始化為也給固定值,如0xA5,在切換任務時,檢查該任務的棧底的幾個值是否是0xA5,如果是,則可粗略判斷為任務棧未溢出,如果不是,則可肯定任務棧一定異常。被踩,或溢出。
pcTaskName
:
- 任務的描述或名字,任務創建時賦值。
- 主要用於調試分析。
- 名字的長度由宏
configMAX_TASK_NAME_LEN
(位於FreeRTOSConfig.h中)指定,包含字符串結束標誌。
pxEndOfStack
:
- 指向任務棧的尾部。
- 該值在堆棧向上生長
portSTACK_GROWTH > 0
,或者開啟記錄堆棧高地址configRECORD_STACK_HIGH_ADDRESS == 1
時有效。 - 也是用任務棧溢出檢測。
uxCriticalNesting
:
- 臨界區嵌套深度記錄值,初始為0。
uxTCBNumber
:
- 標記當前任務控制塊序號,由內核決定,每個任務不同。
- 主要用於調試。
uxTaskNumber
:
- 標記當前任務序號,但不是有內核決定,而是通過API函數
vTaskSetTaskNumber()
來設置的。 - 主要用於調試。
uxBasePriority
:
- 保存任務原來的優先級。
- 主要用於優先級繼承機制。如互斥量。
uxMutexesHeld
:
- 當前任務獲取到的互斥量個數。
- 獲取到一個互斥量,該值+1;釋放一個互斥量,該值-1;為 0 時,優先級恢復基優先級。
pxTaskTag
:
- 任務標籤。
- 內核不使用。
- 類型是任務鉤子函數指針,主要供給用戶使用。
pvThreadLocalStoragePointers
:
- 本地內存指針。
- 其實就是在自己的任務棧里佔用部分內存,並通過接口把這部分內存開放出去,讓其它任務也可以使用。
- 參考:官網
ulRunTimeCounter
:
- 記錄任務在運行狀態下執行的總時間。
- 單位:tickle。
ulNotifiedValue
:
- 任務通知值數組。
ucNotifyState
:
- 任務通知狀態數組。
xNewLib_reent
:
- 還沒研究這有啥用。
ucStaticallyAllocated
:
- 標記任務是動態內存創建還是靜態內存創建。
- 靜態標記為pdTURE。
- 提供給任務回收時使用。
ucDelayAborted
:
- 打斷延時標記。
- 解除掛起時被標記為 pdTURE。
iTaskErrno
:
- 當前任務的錯誤碼。
4.2 創建任務源碼主要內容
主要內容:
- 初始化任務控制塊。
- 初始化任務棧。與主控架構有關。就是把重要數據壓棧,主要是偽造CPU寄存器組壓棧現場。或者說只是偽造上文保護現場,讓下次調用時恢復下文使用。
- 把當前任務插入就緒鏈表。
參考:查看源碼附加部分注釋
4.3 內存申請
一個任務主要由三部分組成:
- 任務主體程序。
- 任務控制塊。
- 任務棧。
任務主體程序一般存在代碼區中。
任務控制塊和任務棧需要的空間有兩種方式申請:
- 靜態申請:定義的方式,佔用的是工程棧,有編譯器決定。
- 動態申請:malloc的方式,佔用的是對應的系統堆空間。
本次講解的函數是動態內存創建任務。
對於任務控制塊和任務棧的空間位置順序也是有講究的,建議是按堆棧增長方向順序,任務控制塊在先,任務棧在後。
這樣做的目的是為了棧溢出時不會踩到任務控制塊:
- 如果堆棧向上生長,先申請任務控制塊空間,再申請任務棧空間。
- 如果堆棧向下生長,先申請任務棧空間,再申請任務控制塊空間。
申請任務控制塊空間:
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); // 申請任務控制塊空間
申請任務棧空間:
pxNewTCB->pxStack = ( StackType_t * ) pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
需要注意,如果申請失敗,已申請部分需要釋放空間再退出。
4.4 任務控制塊初始化
任務控制塊和任務棧都獲得了合法空間,即可開始初始化。
初始化任務控制塊,按照任務控制塊成員進行初始化即可。
主要是調用prvInitialiseNewTask()
API來完成任務初始化。
4.4.1 任務棧地址保存
任務棧地址保存到任務控制塊:(這個在申請空間時實現)
pxNewTCB->pxStack = pxStack;
4.4.2 棧頂對齊糾正
先獲取對齊前的棧頂地址。
再糾正棧頂地址pxTopOfStack
,等等初始化任務棧偽造任務上文現場時就從這個棧頂變量pxTopOfStack
指向的地址開始。
/* 下面兩行用於棧頂地址對齊 */
pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );
pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
/* 檢查棧頂地址堆棧對齊方式是否正確。 */
configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
4.4.3 保存任務名稱
保存任務名稱到任務控制塊,長度受限於宏configMAX_TASK_NAME_LEN
。
保存時遇到0x00結束符結束或受限長度結束,並且會在受限長度末強制加上0x00結束符。
/* 將任務名稱存儲在TCB中 */
if( pcName != NULL )
{
/* 這個for循環用於逐個字符地保存任務名,直到超出限長或遇到結束符為止。 */
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
{
pxNewTCB->pcTaskName[ x ] = pcName[ x ];
/* 遇到結束符,保存並結束 */
if( pcName[ x ] == ( char ) 0x00 )
{
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 字段最後一個字符默認設置為結束符 */
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
}
else
{
/* 任務名傳入NULL,則全字段設置為0x00 */
pxNewTCB->pcTaskName[ 0 ] = 0x00;
}
4.4.4 任務優先級保存
任務優先級會實現斷言式校驗,不能大於等於系統配置的優先級限定值configMAX_PRIORITIES
。
如果優先級超出配置範圍,且沒有開啟斷言式校驗,便會糾正任務優先級值,因為不糾正會存在越界訪問。(就緒表是二級線性表,用數組記錄各個優先級就緒鏈表,優先級會作為數組下標訪問對應就緒鏈表,所以不能讓優先級越界。)
/* 優先級校驗 */
configASSERT( uxPriority < configMAX_PRIORITIES );
if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
/* 若到這裡,優先級超範圍,會重置為最大優先級,防止越界訪問 */
uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
優先級校驗正確,糾正後,保存到任務控制塊,如果開啟了互斥量功能,即是系統當前配置支持了優先級繼承機制,為了實現該機制,任務控制塊會有兩個優先級相關的變量:
pxNewTCB->uxBasePriority
:任務基優先級,優先級繼承機制使用。在優先級繼承狀態時,該值用於保存任務原有優先級。pxNewTCB->uxPriority
:任務在用優先級,實時使用。這個就是任務當前狀態的優先級,是根據這個優先級插入對應就緒鏈表進行搶佔調度的。
/* 確定最終的基優先級,賦值給TCB */
pxNewTCB->uxPriority = uxPriority;
#if ( configUSE_MUTEXES == 1 )
{
/* 使用了互斥量,則會有優先級繼承機制。 */
pxNewTCB->uxBasePriority = uxPriority; /* 優先級繼承 */
pxNewTCB->uxMutexesHeld = 0; /* 當前任務佔用的互斥量 */
}
#endif /* configUSE_MUTEXES */
4.4.5 任務狀態節點
先初始化任務狀態節點。後面完成任務初始前,會把當前任務,即任務狀態節點插入就緒鏈表。
需要設置節點歸屬,這樣才能通過狀態節點找到任務控制塊。
還需要設置任務狀態節點值,就是按這個值排序的,參考任務優先級來配置該值。
- 使用倒敘
onfigMAX_PRIORITIES - uxPriority
是因為鏈表排序採用小在前,而任務優先級採用大優先。
/* 初始化任務狀態鏈表節點 */
vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
/* 設置任務狀態鏈表的當前節點歸屬 */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
/* 根據任務優先級設置事件節點序號 */
listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
任務狀態節點在系統中被插入到不同鏈表而呈現不同的任務狀態:
- 就緒鏈表。就緒態。(在跑就是運行態)
- 延時鏈表。阻塞態。
- 掛起鏈表。阻塞態或者掛起態。
4.4.6 任務事件節點
初始化任務狀態節點,就只是初始化節點而已。還需要設置節點歸屬,這樣才能通過事件節點找到任務控制塊。
/* 初始化時間鏈表節點 */
vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
/* 設置事件鏈表的當前節點歸屬 */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
事件節點用於把任務記錄到各種事件鏈表中,消息隊列阻塞、事件組等等。
4.4.7 任務本地開放內存配置
任務本地開放內存,其實就是在任務棧中取一部分空間出來,通過接口vTaskSetThreadLocalStoragePointer()
和pvTaskGetThreadLocalStoragePointer()
開放給外部使用。
#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
{
/* 初始化本地存儲空間 */
memset( ( void * ) &( pxNewTCB->pvThreadLocalStoragePointers[ 0 ] ), 0x00, sizeof( pxNewTCB->pvThreadLocalStoragePointers ) );
}
#endif
4.4.8 其它值初始化
參考下源碼即可:
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
{
/* 臨界嵌套記錄初始化 */
pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
}
#endif /* portCRITICAL_NESTING_IN_TCB */
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
{
/* 任務標籤初始化 */
pxNewTCB->pxTaskTag = NULL;
}
#endif /* configUSE_APPLICATION_TASK_TAG */
#if ( configGENERATE_RUN_TIME_STATS == 1 )
{
/* 任務佔用CPU總時間值初始化 */
pxNewTCB->ulRunTimeCounter = ( configRUN_TIME_COUNTER_TYPE ) 0;
}
#endif /* configGENERATE_RUN_TIME_STATS */
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
{
/* 初始化任務通知值空間和任務通知狀態空間 */
memset( ( void * ) &( pxNewTCB->ulNotifiedValue[ 0 ] ), 0x00, sizeof( pxNewTCB->ulNotifiedValue ) );
memset( ( void * ) &( pxNewTCB->ucNotifyState[ 0 ] ), 0x00, sizeof( pxNewTCB->ucNotifyState ) );
}
#endif
#if ( INCLUDE_xTaskAbortDelay == 1 )
{
/* 當前任務先標記為沒有被打斷延遲 */
pxNewTCB->ucDelayAborted = pdFALSE;
}
#endif
4.5 任務棧初始化
任務棧初始化主要有以下內容:
- 主要的就是未在上文現場。在調用時能正常恢復出來執行。
- 個人習慣配置。如有些系統喜歡在棧前標記特殊的值,用於dump時判斷任務棧是否正常。
任務棧初始化主要是偽造上文現場,與主控硬件架構有關,調用pxPortInitialiseStack()
來實現,該函數返回初始化後的棧頂地址。
先把整個任務棧初始化為固定的tskSTACK_FILL_BYTE值,方便調試和任務棧溢出和踩棧檢查。
( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
後面讀者自選posix或cortex m3其一學習即可。
4.5.1 posix標準任務棧初始化
因為posix標準下的freertos任務實質是線程,通過posix標準接口實現任務切換。
所以任務棧大概內容就是創建線程,初始化線程管理數據塊,指定任務棧等等。
把線程管理數據結構Thread_t *thread;
固定到棧頂,用於管理實現線程啟停從而實現上層任務切換使用:
Thread_t *thread;
thread = (Thread_t *)(pxTopOfStack + 1) - 1;
初始化線程管理數據結構:
/* 保存任務參數,如任務回調函數及其參數等 */
thread->pxCode = pxCode;
thread->pvParams = pvParameters;
thread->xDying = pdFALSE;
/* 創建一個事件,在任務切換時使用 */
thread->ev = event_create();
初始化線程,指定線程棧:
/* 初始化線程屬性結構體 */
pthread_attr_init( &xThreadAttributes );
/* 指定線程棧 */
pthread_attr_setstack( &xThreadAttributes, pxEndOfStack, ulStackSize );
按照前面配置,創建線程:
/* 進入臨界 */
vPortEnterCritical();
/* 創建線程。posix標準下的freertos模擬器就是使用線程實現task的。 */
iRet = pthread_create( &thread->pthread, &xThreadAttributes, prvWaitForStart, thread );
/* 退出臨界 */
vPortExitCritical();
返回當前棧頂地址:
return pxTopOfStack;
4.5.2 cortex m3任務棧現場偽造
前面章節已經了解了cortex m3內核架構進出異常的知識了,所以偽造現場前段按照異常壓棧部分偽造,當然,對於系統任務切換來說,異常壓棧的那些CPU寄存器組還不完整,需要手動完成其餘CPU寄存器組壓棧。
前段棧使用:
在偽造現場前,先安排好前面棧的用途,然後再開始偽造。
比如我把當前棧頂的前10個位元組初始化為0x55,在調試時就可以方便看到自己的任務棧尾位置;
又比如,像posix標準一樣,把前段棧用於數據管理。
偽造現場,順序不能隨意,需要參考cortex m異常時壓棧處理:
- 硬件壓棧部分:xPSR、PC、LR、R12、R3、R2、R1、R0
- 軟件壓棧部分:R11、R10、R9、R8、R7、R6、R5、R4
StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack,
TaskFunction_t pxCode,
void * pvParameters )
{
/* 偽造棧現場 */
pxTopOfStack--; /* 添加的偏移量,用於解釋MCU在進入/退出中斷時使用堆棧的方式 */
*pxTopOfStack = portINITIAL_XPSR; /* xPSR */
pxTopOfStack--;
*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC */
pxTopOfStack--;
*pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR */
pxTopOfStack -= 5; /* R12, R3, R2 and R1. */
*pxTopOfStack = ( StackType_t ) pvParameters; /* R0 */
pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
return pxTopOfStack;
}
初始化後的棧情況參考圖(圖片源自野火):
4.6 新任務插入就緒表處理
初始化任務控制塊和任務棧後,便可插入就緒鏈表,待調度器調度運行。
調用prvAddNewTaskToReadyList()
實現插入就緒鏈表。
4.6.1 就緒表
freertos就緒表是一個二級線性表,由數組+鏈表組成。
如圖:
各級就緒鏈表都寄存在pxReadyTasksLists
數組中,調度器檢索就緒任務就是從pxReadyTasksLists
數組中,從高優先級開始檢索就緒任務。
另外還有一個變量可以輔助快速檢索就緒任務,uxTopReadyPriority
,就是就緒任務優先級位圖表。
當某個優先級下存在任務就緒,這個值對應bit就會值一,開啟該功能需要限制優先級最大值。cortex m架構的可以了解下前導零指令。
為啥要使用數組+鏈表的方式?本人的認為
- 數組尋址時間複雜度可以達到O(1),但是會浪費空間,但是對於優先級個數,佔用的不多,有效控制好最大優先級即可。
- 而二級使用鏈表是因為,任務數量不定,想像管理優先級一樣管理任務,非常浪費空間,所以鏈表更加適合。
下面處理都進入臨界
4.6.2 就緒表初始化
如果當前新建的任務時第一個,需要初始化就緒表和賦值當前在跑任務全局變量pxCurrentTCB
。
if( pxCurrentTCB == NULL ) /* 判斷創建第一個任務的條件 */
{
/* 把現在需要插入就緒鏈表的任務賦值給整個全局變量吧。pxCurrentTCB表示當前佔用CPU的任務。 */
pxCurrentTCB = pxNewTCB;
if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ) // 才創建第一個任務
{
/* 初始化任務鏈表 */
prvInitialiseTaskLists();
}
}
4.6.3 切換在跑任務
新建的任務如果優先級比當前標記任務更高,而且調度器沒有啟動,可以立即更新該值:
if( pxCurrentTCB != NULL )
{
/* 調度器沒有開啟 */
if( xSchedulerRunning == pdFALSE )
{
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
{
/* 新插入就緒鏈表的任務優先級大於等於當前佔用CPU的任務,切換它 */
pxCurrentTCB = pxNewTCB;
}
}
}
如果調度器已經啟動了,那切換在跑任務的處理就應該交給調度器處理,所以先插入就緒表,退出臨界再觸發任務調度,觸發任務調度實現如下:
/* 如果調度器已經開啟 */
if( xSchedulerRunning != pdFALSE )
{
/* 新插入就緒鏈表的任務優先級比當前佔用CPU的任務優先級高才會切換。 */
if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
{
/* 觸發異常,進行任務切換 */
taskYIELD_IF_USING_PREEMPTION();
}
}
4.6.4 插入就緒表
插入就緒鏈表:
/*
* Place the task represented by pxTCB into the appropriate ready list for the task. It is inserted at the end of the list.
*/
#define prvAddTaskToReadyList( pxTCB ) \
traceMOVED_TASK_TO_READY_STATE( pxTCB ); \
taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \
listINSERT_END( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \
tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB
更新就緒鏈表最高優先級圖位:
/* uxTopReadyPriority holds the priority of the highest priority ready state task. */
#define taskRECORD_READY_PRIORITY( uxPriority ) \
{ \
if( ( uxPriority ) > uxTopReadyPriority ) \
{ \
uxTopReadyPriority = ( uxPriority ); \
} \
} /* taskRECORD_READY_PRIORITY */
插入對應就緒鏈表尾:
-
這個函數只是一個簡單的插入鏈表的API,數據結構的基礎。但是這裡的重點不是這個API,而是這個API的參數。
-
listINSERT_END( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) );
- 就緒鏈表不是一個循環雙向鏈表,freertos的就緒鏈表是一個二級線性表。由數組+鏈表組成。
- 由一個數組管理各級就緒鏈表。
/* 這只是一個數據結構-鏈表相關的API */
#define listINSERT_END( pxList, pxNewListItem ) \
{ \
ListItem_t * const pxIndex = ( pxList )->pxIndex; \
\
/* Only effective when configASSERT() is also defined, these tests may catch \
* the list data structures being overwritten in memory. They will not catch \
* data errors caused by incorrect configuration or use of FreeRTOS. */ \
listTEST_LIST_INTEGRITY( ( pxList ) ); \
listTEST_LIST_ITEM_INTEGRITY( ( pxNewListItem ) ); \
\
/* Insert a new list item into ( pxList ), but rather than sort the list, \
* makes the new list item the last item to be removed by a call to \
* listGET_OWNER_OF_NEXT_ENTRY(). */ \
( pxNewListItem )->pxNext = pxIndex; \
( pxNewListItem )->pxPrevious = pxIndex->pxPrevious; \
\
pxIndex->pxPrevious->pxNext = ( pxNewListItem ); \
pxIndex->pxPrevious = ( pxNewListItem ); \
\
/* Remember which list the item is in. */ \
( pxNewListItem )->pxContainer = ( pxList ); \
\
( ( pxList )->uxNumberOfItems )++; \
}
4.7 刪除任務源碼
主要是釋放資源。
如果是刪除自己的話,就插入到結束鏈表xTasksWaitingTermination
。
- 因為任務調度時需要上下文切換,所以為了保證調度器能順利切換到下一個任務,便把釋放資源,刪除任務的內容交給空閑任務處理。
如果不是刪除本身,立即就刪除,無需經過空閑任務處理。
處理需要進入臨界處理。
4.7.1 相關變量
uxDeletedTasksWaitingCleanUp
:這個值表示當前有多少人任務需要釋放。空閑任務會檢查這個值。
xTasksWaitingTermination
:結束鏈表。空閑任務調用 prvCheckTasksWaitingTermination()
函數來檢查該鏈表並釋放資源。
4.7.2 解除任務所有狀態
通過任務句柄獲取任務控制塊:
/* 獲取任務控制塊。若傳入任務句柄為空,則返回當前運行的任務的任務控制塊 */
pxTCB = prvGetTCBFromHandle( xTaskToDelete );
解除任務所有狀態,即是從相關狀態鏈表中移除當前任務:
/* 把任務從狀態鏈表(就緒鏈表、延時鏈表這些)中移除。 */
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
解除事件阻塞:
/* 如果任務在等待某個事件,也把任務從該事件鏈表中移除。 */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
4.7.3 刪除本身
傳入任務句柄為NULL,表示刪除本身,但是任務調度時需要上下文切換,所以為了保證調度器能順利切換到下一個任務,便把釋放資源,刪除任務的內容交給空閑任務處理。
先把當前任務插入到結束鏈表xTasksWaitingTermination
,更新uxDeletedTasksWaitingCleanUp
,讓空閑任務知道有多少個已刪除的任務需要進行內存釋放:
/* 要是刪除自己的話 */
if( pxTCB == pxCurrentTCB )
{
/* 刪除自己任務函數不能在任務本身內完成,因為需要上下文切換到另一個任務。
所以需要將任務放在結束列表中(xTasksWaitingTermination);
空閑任務會檢查結束列表並在空閑任務中釋放刪除任務的控制塊和已刪除任務的堆棧內存。 */
vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );
/* 增加 uxDeletedTasksWaitingCleanUp 變量的值,
該變量用於記錄有多少個任務需要釋放內存,以便空閑任務知道有多少個已刪除的任務需要進行內存釋放。 */
++uxDeletedTasksWaitingCleanUp;
/* 刪除任務鉤子函數 */
portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
}
4.7.4 刪除其它任務
刪除的任務非當前在跑任務。可以在這裡就做刪除處理,釋放資源。
當前有效任務統計uxCurrentNumberOfTasks
減一,還要重置下一個預期的解鎖時間,以防它被引用被刪除的任務:
prvResetNextTaskUnblockTime()
需要在臨界內處理,因為內部涉及到延時機制組件的處理,如延時鏈表pxDelayedTaskList
、未來最近喚醒時間變量xNextTaskUnblockTime
的處理,這些變量在系統節拍中斷回調中用到。
taskENTER_CRITICAL();
if( pxTCB != pxCurrentTCB )
{
/* 當前任務數量減一 */
--uxCurrentNumberOfTasks;
/* 重置下一個預期的解鎖時間,以防它被引用被刪除的任務。 */
prvResetNextTaskUnblockTime();
}
taskEXIT_CRITICAL();
然後調用prvDeleteTCB()
釋放資源
if( pxTCB != pxCurrentTCB )
{
/* 釋放資源 */
prvDeleteTCB( pxTCB );
}
4.7.5 觸發任務調度
如果調度器沒有關閉,且刪除了本身,那需要觸發任務調度,切換到其它有效任務:
/* 如果調度器沒有關閉 */
if( xSchedulerRunning != pdFALSE )
{
if( pxTCB == pxCurrentTCB )
{
/* 自刪除要觸發異常,進行任務調度 */
configASSERT( uxSchedulerSuspended == 0 );
portYIELD_WITHIN_API();
}
}
4.7.6 空閑任務釋放被刪除任務資源
在空閑任務中調用prvCheckTasksWaitingTermination()
來處理在結束鏈表xTasksWaitingTermination
中的任務。
需要注意的是,在系統中,需要留點CPU時間給空閑任務,要不然刪除本身的任務資源久久得不到釋放。
static void prvCheckTasksWaitingTermination( void )
{
#if ( INCLUDE_vTaskDelete == 1 )
{
TCB_t * pxTCB;
/* 一直刪除到沒有刪除任務為止 */
while( uxDeletedTasksWaitingCleanUp > ( UBaseType_t ) 0U )
{
/* 進入臨界 */
taskENTER_CRITICAL();
{
/* 檢查結束列表中的任務 */
pxTCB = listGET_OWNER_OF_HEAD_ENTRY( ( &xTasksWaitingTermination ) );
/* 將任務從狀態列表中刪除 */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
--uxCurrentNumberOfTasks;
--uxDeletedTasksWaitingCleanUp;
}
/* 退出臨界 */
taskEXIT_CRITICAL();
/* 刪除任務控制塊與堆棧 */
prvDeleteTCB( pxTCB );
}
}
#endif /* INCLUDE_vTaskDelete */
}
4.7.7 釋放任務空間函數prvDeleteTCB()
不管在哪裡釋放資源,最終都是調用prvDeleteTCB()
API來實現。
釋放資源主要是任務控制塊空間和任務棧空間,前期需要先判斷是否是動態分配,動態分配才能動態釋放。
先了解幾個參數或宏:
-
configSUPPORT_DYNAMIC_ALLOCATION
:動態分配內存宏- 定義為 1 :在創建 FreeRTOS的內核對象時候 所需要的 RAM 就會從 FreeRTOS 的堆中動態的獲取內存。
- 定義為 0:需要用戶自行提供。
- 默認為1。
-
configSUPPORT_STATIC_ALLOCATION
:靜態分配內存宏- 定義為1:允許靜態創建任務。
- 定義為0:不允許靜態創建任務。
-
pxTCB->ucStaticallyAllocated
:任務分配內存記錄tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB
:動態分配任務控制塊和任務棧。tskSTATICALLY_ALLOCATED_STACK_ONLY
:只是靜態分配了任務棧。任務控制塊是動態分配的。tskSTATICALLY_ALLOCATED_STACK_AND_TCB
:靜態分配任務控制塊和任務棧。
根據上述參數可以了解到當前任務的任務棧和任務控制塊是如何分配的,把動態分配的動態釋放即可。
static void prvDeleteTCB( TCB_t * pxTCB )
{
/* 這個調用特別需要TriCore端口。它必須位於vPortFree()調用的上方。這個調用也被那些想要靜態分配和清理RAM的端口/演示程序所使用。 */
portCLEAN_UP_TCB( pxTCB );
#if ( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) && ( portUSING_MPU_WRAPPERS == 0 ) )
{
/* 釋放動態分配的任務控制塊和任務棧空間 */
vPortFreeStack( pxTCB->pxStack );
vPortFree( pxTCB );
}
#elif ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
{
/* 開啟了靜態分配功能,就需要檢查任務控制塊和任務棧空間是靜態還是動態分配的 */
if( pxTCB->ucStaticallyAllocated == tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB )
{
/* 釋放動態分配的任務控制塊和任務棧空間 */
vPortFreeStack( pxTCB->pxStack );
vPortFree( pxTCB );
}
else if( pxTCB->ucStaticallyAllocated == tskSTATICALLY_ALLOCATED_STACK_ONLY )
{
/* 只有任務棧是靜態分配的,那就只釋放TCB的內存 */
vPortFree( pxTCB );
}
else
{
/* 堆棧和TCB都不是動態分配的,因此不需要釋放任何東西 */
configASSERT( pxTCB->ucStaticallyAllocated == tskSTATICALLY_ALLOCATED_STACK_AND_TCB );
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
}
附件
xTaskCreate():創建任務源碼
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
{
TCB_t * pxNewTCB;
BaseType_t xReturn;
/* 根據堆棧生長方式不同,申請任務控制塊和任務棧的順序不同,保證任務棧溢出不會踩到任務控制塊。*/
#if ( portSTACK_GROWTH > 0 ) // 堆棧向上生長
{
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); // 申請任務控制塊空間
if( pxNewTCB != NULL )
{
/* 繼續申請任務堆棧空間 */
pxNewTCB->pxStack = ( StackType_t * ) pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
if( pxNewTCB->pxStack == NULL )
{
/* 無法分配堆棧。刪除已分配的TCB */
vPortFree( pxNewTCB );
pxNewTCB = NULL;
}
}
}
#else /* portSTACK_GROWTH */ // 堆棧向下生長
{
StackType_t * pxStack;
/* 先申請任務棧空間 */
pxStack = pvPortMallocStack( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
if( pxStack != NULL )
{
/* 申請任務控制塊空間 */
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
if( pxNewTCB != NULL )
{
/* 保存任務棧地址到任務控制塊 */
pxNewTCB->pxStack = pxStack;
}
else
{
/* The stack cannot be used as the TCB was not created. Free
* it again. */
vPortFreeStack( pxStack );
}
}
else
{
pxNewTCB = NULL;
}
}
#endif /* portSTACK_GROWTH */
if( pxNewTCB != NULL )
{
#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e9029 !e731 Macro has been consolidated for readability reasons. */
{
/* Tasks can be created statically or dynamically, so note this
* task was created dynamically in case it is later deleted. */
pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB; // 標記任務創建的方式
}
#endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */
/* 初始化任務棧 */
prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
/* 把當前任務插入就緒鏈表 */
prvAddNewTaskToReadyList( pxNewTCB );
xReturn = pdPASS;
}
else
{
xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
}
return xReturn;
}
prvInitialiseNewTask():任務初始化函數
static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,
const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask,
TCB_t * pxNewTCB,
const MemoryRegion_t * const xRegions )
{
StackType_t * pxTopOfStack;
UBaseType_t x;
#if ( portUSING_MPU_WRAPPERS == 1 ) // 不使用,略
/* Should the task be created in privileged mode? */
BaseType_t xRunPrivileged;
if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
{
xRunPrivileged = pdTRUE;
}
else
{
xRunPrivileged = pdFALSE;
}
uxPriority &= ~portPRIVILEGE_BIT;
#endif /* portUSING_MPU_WRAPPERS == 1 */
#if ( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 1 )
{
/* 意思是把整個任務棧初始化為固定的tskSTACK_FILL_BYTE值。這操作主要用於調試和任務棧溢出檢查。 */
( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
}
#endif /* tskSET_NEW_STACKS_TO_KNOWN_VALUE */
#if ( portSTACK_GROWTH < 0 ) // 堆棧向下生長
{
/* 下面兩行用於棧頂地址對齊 */
pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );
pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
/* 檢查棧頂地址堆棧對齊方式是否正確。 */
configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
#if ( configRECORD_STACK_HIGH_ADDRESS == 1 )
{
/* 記錄棧尾地址 */
pxNewTCB->pxEndOfStack = pxTopOfStack;
}
#endif /* configRECORD_STACK_HIGH_ADDRESS */
}
#else /* portSTACK_GROWTH */ // 堆棧向上生長,參考向下分析即可。略
{
pxTopOfStack = pxNewTCB->pxStack;
/* Check the alignment of the stack buffer is correct. */
configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );
/* The other extreme of the stack space is required if stack checking is
* performed. */
pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
}
#endif /* portSTACK_GROWTH */
/* 將任務名稱存儲在TCB中 */
if( pcName != NULL )
{
/* 這個for循環用於逐個字符地保存任務名,直到超出限長或遇到結束符為止。 */
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
{
pxNewTCB->pcTaskName[ x ] = pcName[ x ];
/* 遇到結束符,保存並結束 */
if( pcName[ x ] == ( char ) 0x00 )
{
break;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 字段最後一個字符默認設置為結束符 */
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
}
else
{
/* 任務名傳入NULL,則全字段設置為0x00 */
pxNewTCB->pcTaskName[ 0 ] = 0x00;
}
/* This is used as an array index so must ensure it's not too large. */
/* 優先級校驗 */
configASSERT( uxPriority < configMAX_PRIORITIES );
if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
/* 若到這裡,優先級超範圍,會重置為最大優先級 */
uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
else
{
/* 調試用的測試回調函數 */
mtCOVERAGE_TEST_MARKER();
}
/* 確定最終的基優先級,賦值給TCB */
pxNewTCB->uxPriority = uxPriority;
#if ( configUSE_MUTEXES == 1 )
{
/* 使用了互斥量,則會有優先級繼承機制。 */
pxNewTCB->uxBasePriority = uxPriority; /* 優先級繼承 */
pxNewTCB->uxMutexesHeld = 0; /* 當前任務佔用的互斥量 */
}
#endif /* configUSE_MUTEXES */
vListInitialiseItem( &( pxNewTCB->xStateListItem ) ); /* 初始化任務狀態鏈表節點 */
vListInitialiseItem( &( pxNewTCB->xEventListItem ) ); /* 初始化時間鏈表節點 */
/* 設置任務狀態鏈表的當前節點歸屬 */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
/* 根據任務優先級設置事件節點序號 */
listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
/* 設置事件鏈表的當前節點歸屬 */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
{
/* 臨界嵌套記錄初始化 */
pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
}
#endif /* portCRITICAL_NESTING_IN_TCB */
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
{
/* 任務標籤初始化 */
pxNewTCB->pxTaskTag = NULL;
}
#endif /* configUSE_APPLICATION_TASK_TAG */
#if ( configGENERATE_RUN_TIME_STATS == 1 )
{
/* 任務佔用CPU總時間值初始化 */
pxNewTCB->ulRunTimeCounter = ( configRUN_TIME_COUNTER_TYPE ) 0;
}
#endif /* configGENERATE_RUN_TIME_STATS */
#if ( portUSING_MPU_WRAPPERS == 1 ) // 略
{
vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, ulStackDepth );
}
#else
{
/* Avoid compiler warning about unreferenced parameter. */
( void ) xRegions;
}
#endif
#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
{
/* 初始化本地存儲空間 */
memset( ( void * ) &( pxNewTCB->pvThreadLocalStoragePointers[ 0 ] ), 0x00, sizeof( pxNewTCB->pvThreadLocalStoragePointers ) );
}
#endif
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
{
/* 初始化任務通知值空間和任務通知狀態空間 */
memset( ( void * ) &( pxNewTCB->ulNotifiedValue[ 0 ] ), 0x00, sizeof( pxNewTCB->ulNotifiedValue ) );
memset( ( void * ) &( pxNewTCB->ucNotifyState[ 0 ] ), 0x00, sizeof( pxNewTCB->ucNotifyState ) );
}
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 ) // 略
{
/* Initialise this task's Newlib reent structure.
* See the third party link //www.nadler.com/embedded/newlibAndFreeRTOS.html
* for additional information. */
_REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
}
#endif
#if ( INCLUDE_xTaskAbortDelay == 1 )
{
/* 當前任務先標記為沒有被打斷延遲 */
pxNewTCB->ucDelayAborted = pdFALSE;
}
#endif
#if ( portUSING_MPU_WRAPPERS == 1 ) // 略
{
/* If the port has capability to detect stack overflow,
* pass the stack end address to the stack initialization
* function as well. */
#if ( portHAS_STACK_OVERFLOW_CHECKING == 1 )
{
#if ( portSTACK_GROWTH < 0 )
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters, xRunPrivileged );
}
#else /* portSTACK_GROWTH */
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters, xRunPrivileged );
}
#endif /* portSTACK_GROWTH */
}
#else /* portHAS_STACK_OVERFLOW_CHECKING */
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );
}
#endif /* portHAS_STACK_OVERFLOW_CHECKING */
}
#else /* portUSING_MPU_WRAPPERS */
{
#if ( portHAS_STACK_OVERFLOW_CHECKING == 1 ) // 打開棧溢監測出功能
{
#if ( portSTACK_GROWTH < 0 ) // 堆棧向下生長
{
/* 初始化任務棧:偽造CPU異常上文保護現場。與主控硬件架構有關 */
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters );
}
#else /* portSTACK_GROWTH */
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters );
}
#endif /* portSTACK_GROWTH */
}
#else /* portHAS_STACK_OVERFLOW_CHECKING */
{
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
}
#endif /* portHAS_STACK_OVERFLOW_CHECKING */
}
#endif /* portUSING_MPU_WRAPPERS */
if( pxCreatedTask != NULL )
{
/* 讓任務句柄指向任務控制塊 */
*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
pxPortInitialiseStack():POSIX標準任務棧初始化函數
portSTACK_TYPE *pxPortInitialiseStack( portSTACK_TYPE *pxTopOfStack,
portSTACK_TYPE *pxEndOfStack,
pdTASK_CODE pxCode, void *pvParameters )
{
Thread_t *thread;
pthread_attr_t xThreadAttributes;
size_t ulStackSize;
int iRet;
/* 配置整個系統中,在某個線程只執行一次prvSetupSignalsAndSchedulerPolicy() */
(void)pthread_once( &hSigSetupThread, prvSetupSignalsAndSchedulerPolicy );
/* 將額外的線程數據存儲在堆棧的開頭 */
thread = (Thread_t *)(pxTopOfStack + 1) - 1; // 把棧頂指針,賦值給線程管理結構體指針。意思是把線程管理結構體的數據在任務棧初始棧頂上固定使用。
pxTopOfStack = (portSTACK_TYPE *)thread - 1; // 重新賦值棧頂指針。
ulStackSize = (pxTopOfStack + 1 - pxEndOfStack) * sizeof(*pxTopOfStack); // 計算剩下的任務棧大小,在後面配置為線程棧。
/* 保存任務參數,如任務回調函數及其參數等 */
thread->pxCode = pxCode;
thread->pvParams = pvParameters;
thread->xDying = pdFALSE;
/* 初始化線程屬性結構體 */
pthread_attr_init( &xThreadAttributes );
/* 指定線程棧 */
pthread_attr_setstack( &xThreadAttributes, pxEndOfStack, ulStackSize );
/* 創建一個事件,在任務切換時使用 */
thread->ev = event_create();
/* 進入臨界 */
vPortEnterCritical();
/* 創建線程。posix標準下的freertos模擬器就是使用線程實現task的。 */
iRet = pthread_create( &thread->pthread, &xThreadAttributes,
prvWaitForStart, thread );
if ( iRet )
{
prvFatalError( "pthread_create", iRet );
}
/* 退出臨界 */
vPortExitCritical();
return pxTopOfStack;
}
pxPortInitialiseStack():cortex m3/m4任務棧現場偽造
StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack,
TaskFunction_t pxCode,
void * pvParameters )
{
/* 偽造棧現場 */
pxTopOfStack--; /* 添加的偏移量,用於解釋MCU在進入/退出中斷時使用堆棧的方式 */
*pxTopOfStack = portINITIAL_XPSR; /* xPSR */
pxTopOfStack--;
*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC */
pxTopOfStack--;
*pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR */
pxTopOfStack -= 5; /* R12, R3, R2 and R1. */
*pxTopOfStack = ( StackType_t ) pvParameters; /* R0 */
pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
return pxTopOfStack;
}
prvAddNewTaskToReadyList():插入任務就緒鏈表函數
static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB )
{
/* Ensure interrupts don't access the task lists while the lists are being
* updated. */
/* 進入臨界 */
taskENTER_CRITICAL();
{
uxCurrentNumberOfTasks++; // 全局變量,用於任務計數。
if( pxCurrentTCB == NULL )
{
/* There are no other tasks, or all the other tasks are in
* the suspended state - make this the current task. */
/* 把現在需要插入就緒鏈表的任務賦值給整個全局變量吧。pxCurrentTCB表示當前佔用CPU的任務。 */
pxCurrentTCB = pxNewTCB;
if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ) // 才創建第一個任務
{
/* This is the first task to be created so do the preliminary
* initialisation required. We will not recover if this call
* fails, but we will report the failure. */
/* 初始化任務鏈表 */
prvInitialiseTaskLists();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* If the scheduler is not already running, make this task the
* current task if it is the highest priority task to be created
* so far. */
/* 調度器沒有開啟 */
if( xSchedulerRunning == pdFALSE )
{
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
{
/* 新插入就緒鏈表的任務優先級大於等於當前佔用CPU的任務,切換它 */
pxCurrentTCB = pxNewTCB;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
uxTaskNumber++;
#if ( configUSE_TRACE_FACILITY == 1 )
{
/* Add a counter into the TCB for tracing only. */
pxNewTCB->uxTCBNumber = uxTaskNumber;
}
#endif /* configUSE_TRACE_FACILITY */
traceTASK_CREATE( pxNewTCB );
/* 插入就緒鏈表 */
prvAddTaskToReadyList( pxNewTCB );
portSETUP_TCB( pxNewTCB );
}
/* 退出臨界 */
taskEXIT_CRITICAL();
/* 如果調度器已經開啟 */
if( xSchedulerRunning != pdFALSE )
{
/* If the created task is of a higher priority than the current task
* then it should run now. */
/* 新插入就緒鏈表的任務優先級比當前佔用CPU的任務優先級高才會切換。 */
if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
{
/* 觸發異常,進行任務切換 */
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
vTaskDelete():刪除任務源碼
void vTaskDelete( TaskHandle_t xTaskToDelete )
{
TCB_t * pxTCB;
/* 進入臨界 */
taskENTER_CRITICAL();
{
/* 獲取任務控制塊。若傳入任務句柄為空,則返回當前運行的任務的任務控制塊 */
pxTCB = prvGetTCBFromHandle( xTaskToDelete );
/* 把任務從狀態鏈表(就緒鏈表、延時鏈表這些)中移除。 */
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 如果任務在等待某個事件,也把任務從該事件鏈表中移除。 */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
uxTaskNumber++;
/* 要是刪除自己的話 */
if( pxTCB == pxCurrentTCB )
{
/* 刪除自己任務函數不能在任務本身內完成,因為需要上下文切換到另一個任務。
所以需要將任務放在結束列表中(xTasksWaitingTermination);
空閑任務會檢查結束列表並在空閑任務中釋放刪除任務的控制塊和已刪除任務的堆棧內存。 */
vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );
/* 增加 uxDeletedTasksWaitingCleanUp 變量的值,
該變量用於記錄有多少個任務需要釋放內存,以便空閑任務知道有多少個已刪除的任務需要進行內存釋放。 */
++uxDeletedTasksWaitingCleanUp;
traceTASK_DELETE( pxTCB );
/* 刪除任務鉤子函數 */
portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
}
else
{
/* 當前任務數量減一 */
--uxCurrentNumberOfTasks;
traceTASK_DELETE( pxTCB );
/* 重置下一個預期的解鎖時間,以防它被引用被刪除的任務。 */
prvResetNextTaskUnblockTime();
}
}
taskEXIT_CRITICAL();
/* 如果不是自刪除,則直接刪除任務控制塊 */
if( pxTCB != pxCurrentTCB )
{
prvDeleteTCB( pxTCB );
}
/* 如果調度器沒有關閉 */
if( xSchedulerRunning != pdFALSE )
{
if( pxTCB == pxCurrentTCB )
{
/* 自刪除要觸發異常,進行任務調度 */
configASSERT( uxSchedulerSuspended == 0 );
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
prvCheckTasksWaitingTermination():空閑任務檢索結束鏈表釋放資源
static void prvCheckTasksWaitingTermination( void )
{
#if ( INCLUDE_vTaskDelete == 1 )
{
TCB_t * pxTCB;
/* 一直刪除到沒有刪除任務為止 */
while( uxDeletedTasksWaitingCleanUp > ( UBaseType_t ) 0U )
{
/* 進入臨界 */
taskENTER_CRITICAL();
{
/* 檢查結束列表中的任務 */
pxTCB = listGET_OWNER_OF_HEAD_ENTRY( ( &xTasksWaitingTermination ) );
/* 將任務從狀態列表中刪除 */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
--uxCurrentNumberOfTasks;
--uxDeletedTasksWaitingCleanUp;
}
/* 退出臨界 */
taskEXIT_CRITICAL();
/* 刪除任務控制塊與堆棧 */
prvDeleteTCB( pxTCB );
}
}
#endif /* INCLUDE_vTaskDelete */
}
prvDeleteTCB():刪除任務控制塊和任務堆棧
static void prvDeleteTCB( TCB_t * pxTCB )
{
/* 這個調用特別需要TriCore端口。它必須位於vPortFree()調用的上方。這個調用也被那些想要靜態分配和清理RAM的端口/演示程序所使用。 */
portCLEAN_UP_TCB( pxTCB );
#if ( configUSE_NEWLIB_REENTRANT == 1 )
{
/* 沒有用過,還不曉得咋用 */
_reclaim_reent( &( pxTCB->xNewLib_reent ) );
}
#endif /* configUSE_NEWLIB_REENTRANT */
#if ( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) && ( portUSING_MPU_WRAPPERS == 0 ) )
{
/* 釋放動態分配的任務控制塊和任務棧空間 */
vPortFreeStack( pxTCB->pxStack );
vPortFree( pxTCB );
}
#elif ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
{
/* 開啟了靜態分配功能,就需要檢查任務控制塊和任務棧空間是靜態還是動態分配的 */
if( pxTCB->ucStaticallyAllocated == tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB )
{
/* 釋放動態分配的任務控制塊和任務棧空間 */
vPortFreeStack( pxTCB->pxStack );
vPortFree( pxTCB );
}
else if( pxTCB->ucStaticallyAllocated == tskSTATICALLY_ALLOCATED_STACK_ONLY )
{
/* 只有堆棧是靜態分配的,那就只釋放TCB的內存 */
vPortFree( pxTCB );
}
else
{
/* 堆棧和TCB都不是動態分配的,因此不需要釋放任何東西 */
configASSERT( pxTCB->ucStaticallyAllocated == tskSTATICALLY_ALLOCATED_STACK_AND_TCB );
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
}