前言
後面都是已動态記憶體任務為例來分析。
注意:
- 由于目前學習是在linux上跑的freertos,對于freertos底層相關接口,從demo工程來看,都是posix标準相關。
- 鑒于freertos多用于ARM架構,本教程涉及到硬體接口,作者會分兩條路線講解:
- posix标準接口。
- cortex m3/4架構相關接口。
參考:
- 博文源自李柱明部落格:https://www.cnblogs.com/lizhuming/p/16072375.html
本文預設按堆棧向下生長方式講解。
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,
必須位于結構體的第二項,用于MPU設定。xMPUSettings
xStateListItem
和
xEventListItem
:
- 狀态連結清單節點和事件連結清單節點。
- 這些連結清單主要被OS排程器使用,用于跟蹤、處理任務。
- 對于連結清單的學習可以百度搜尋下:李柱明 連結清單
uxPriority
:
- 任務優先級,freertos是0為最低優先級。
- 一般在建立任務時配置,也可以動态修改。對于動态修改,後面任務控制章節會講述。
pxStack
:
- 任務棧底指針,指向任務堆棧的起始位置。
- 在任務建立時就被指派了。
- 棧底指針
被指派後就不會改變的,而棧頂指針pxStack
是會随着出入棧變化的。pxTopOfStack
- 對于向下生長的棧,該值可用于任務棧溢出監測。在任務棧初始化時,會初始化為也給固定值,如0xA5,在切換任務時,檢查該任務的棧底的幾個值是否是0xA5,如果是,則可粗略判斷為任務棧未溢出,如果不是,則可肯定任務棧一定異常。被踩,或溢出。
pcTaskName
:
- 任務的描述或名字,任務建立時指派。
- 主要用于調試分析。
- 名字的長度由宏
(位于FreeRTOSConfig.h中)指定,包含字元串結束标志。configMAX_TASK_NAME_LEN
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 記憶體申請
一個任務主要由三部分組成:
- 任務主體程式。
- 任務控制塊。
- 任務棧。
任務主體程式一般存在代碼區中。
任務控制塊和任務棧需要的空間有兩種方式申請:
- 靜态申請:非freertos内部動态配置設定方式。
- 動态申請:freertos内部動态配置設定的方式,占用的是對應的系統堆空間。
本次講解的函數是動态記憶體建立任務。
對于任務控制塊和任務棧的空間位置順序也是有講究的,建議是按堆棧增長方向順序,任務控制塊在先,任務棧在後。
這樣做的目的是為了棧溢出時不會踩到任務控制塊:
- 如果堆棧向上生長,先申請任務控制塊空間,再申請任務棧空間。
-
【freertos】004-任務在核心實作細節 - 如果堆棧向下生長,先申請任務棧空間,再申請任務控制塊空間。
-
【freertos】004-任務在核心實作細節
申請任務控制塊空間:
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 http://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 */
}