【freertos】004-任務建立與刪除及其實現細節

李柱明發表於2022-03-29

前言

後面都是已動態記憶體任務為例來分析。

注意:

  • 由於當前學習是在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設定。

xStateListItemxEventListItem

  • 狀態連結串列節點和事件連結串列節點。
  • 這些連結串列主要被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 建立任務原始碼主要內容

主要內容:

  1. 初始化任務控制塊。
  2. 初始化任務棧。與主控架構有關。就是把重要資料壓棧,主要是偽造CPU暫存器組壓棧現場。或者說只是偽造上文保護現場,讓下次呼叫時恢復下文使用。
  3. 把當前任務插入就緒連結串列。

參考:檢視原始碼附加部分註釋

4.3 記憶體申請

一個任務主要由三部分組成:

  1. 任務主體程式。
  2. 任務控制塊。
  3. 任務棧。

任務主體程式一般存在程式碼區中。

任務控制塊和任務棧需要的空間有兩種方式申請:

  1. 靜態申請:定義的方式,佔用的是工程棧,有編譯器決定。
  2. 動態申請: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;
}

優先順序校驗正確,糾正後,儲存到任務控制塊,如果開啟了互斥量功能,即是系統當前配置支援了優先順序繼承機制,為了實現該機制,任務控制塊會有兩個優先順序相關的變數:

  1. pxNewTCB->uxBasePriority:任務基優先順序,優先順序繼承機制使用。在優先順序繼承狀態時,該值用於儲存任務原有優先順序。
  2. 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 );

任務狀態節點在系統中被插入到不同連結串列而呈現不同的任務狀態:

  1. 就緒連結串列。就緒態。(在跑就是執行態)
  2. 延時連結串列。阻塞態。
  3. 掛起連結串列。阻塞態或者掛起態。

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 任務棧初始化

任務棧初始化主要有以下內容:

  1. 主要的就是未在上文現場。在呼叫時能正常恢復出來執行。
  2. 個人習慣配置。如有些系統喜歡在棧前標記特殊的值,用於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 */
}

相關文章