首頁 > 軟體

FreeRTOS進階之任務建立完全解析

2022-04-09 04:00:15

前言

在FreeRTOS基礎系列 FreeRTOS任務建立和刪除中介紹了任務建立API函數xTaskCreate(),我們這裡先回顧一下這個函數的宣告:

        BaseType_t xTaskCreate(
                            TaskFunction_tp vTaskCode,
                            const char * constpcName,
                            unsigned short usStackDepth,
                            void *pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t *pvCreatedTask
                          );

這個API函數的作用是建立新的任務並將它加入到任務就緒列表,函數引數含義為:

pvTaskCode:函數指標,指向任務函數的入口。任務永遠不會返回(位於死迴圈內)。該引數型別TaskFunction_t定義在檔案projdefs.h中,定義為:typedef void(*TaskFunction_t)( void * ),即引數為空指標型別並返回空型別。

pcName:任務描述。主要用於偵錯。字串的最大長度(包括字串結束字元)由宏configMAX_TASK_NAME_LEN指定,該宏位於FreeRTOSConfig.h檔案中。

usStackDepth:指定任務堆疊大小,能夠支援的堆疊變數數量(堆疊深度),而不是位元組數。

比如,在16位元寬度的堆疊下,usStackDepth定義為100,則實際使用200位元組堆疊儲存空間。堆疊的寬度乘以深度必須不超過size_t型別所能表示的最大值。

比如,size_t為16位元,則可以表示堆疊的最大值是65535位元組。這是因為堆疊在申請時是以位元組為單位的,申請的位元組數就是堆疊寬度乘以深度,如果這個乘積超出size_t所表示的範圍,就會溢位,分配的堆疊空間也不是我們想要的。

pvParameters:指標,當任務建立時,作為一個引數傳遞給任務。

uxPriority:任務的優先順序。具有MPU支援的系統,可以通過置位優先順序引數的portPRIVILEGE_BIT位,隨意的在特權(系統)模式下建立任務。

比如,建立一個優先順序為2的特權任務,引數uxPriority可以設定為 ( 2 | portPRIVILEGE_BIT )。

pvCreatedTask:用於回傳一個控制程式碼(ID),建立任務後可以使用這個控制程式碼參照任務。

雖然xTaskCreate()看上去很像函數,但其實是一個宏,真正被呼叫的函數是xTaskGenericCreate(),xTaskCreate()宏定義如下所示:

#define xTaskCreate( pvTaskCode, pcName, usStackDepth,pvParameters, uxPriority, pxCreatedTask )    
      xTaskGenericCreate( ( pvTaskCode ),( pcName ), ( usStackDepth ), ( pvParameters ), ( uxPriority ), ( pxCreatedTask), ( NULL ), ( NULL ), ( NULL ) )

可以看到,xTaskCreate比xTaskGenericCreate少了三個引數,在宏定義中,這三個引數被設定為NULL。這三個引數用於使用靜態變數的方法分配堆疊、任務TCB空間以及設定MPU相關的引數。

一般情況下,這三個引數是不使用的,所以任務建立宏xTaskCreate定義的時候,將這三個引數對使用者隱藏了。接下來的章節中,為了方便,我們還是稱xTaskCreate()為函數,雖然它是一個宏定義。

上面我們提到了任務TCB(任務控制塊),這是一個需要重點介紹的關鍵點。它用於儲存任務的狀態資訊,包括任務執行時的環境。每個任務都有自己的任務TCB。

任務TCB是一個相對比較大的資料結構,這也是情理之中的,因為與任務相關的程式碼佔到整個FreeRTOS程式碼量的一半左右,這些程式碼大都與任務TCB相關,我們先來介紹一下任務TCB資料結構的定義:

typedef struct tskTaskControlBlock
{
    volatile StackType_t    *pxTopOfStack; /*當前堆疊的棧頂,必須位於結構體的第一項*/
    #if ( portUSING_MPU_WRAPPERS == 1 )
        xMPU_SETTINGS   xMPUSettings;      /*MPU設定,必須位於結構體的第二項*/
    #endif
    ListItem_t          xStateListItem; /*任務的狀態列表項,以參照的方式表示任務的狀態*/
    ListItem_t          xEventListItem;    /*事件列表項,用於將任務以參照的方式掛接到事件列表*/
    UBaseType_t         uxPriority;        /*儲存任務優先順序,0表示最低優先順序*/
    StackType_t         *pxStack;           /*指向堆疊的起始位置*/
    char               pcTaskName[ configMAX_TASK_NAME_LEN ];/*任務名字*/
    #if ( portSTACK_GROWTH > 0 )
        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;      /*儲存一個特定數值*/
    #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 )
        uint32_t        ulRunTimeCounter;  /*記錄任務在執行狀態下執行的總時間*/
    #endif
    #if ( configUSE_NEWLIB_REENTRANT == 1 )
        /* 為任務分配一個Newlibreent結構體變數。Newlib是一個C庫函數,並非FreeRTOS維護,FreeRTOS也不對使用結果負責。如果使用者使用Newlib,必須熟知Newlib的細節*/
        struct _reent xNewLib_reent;
    #endif
    #if( configUSE_TASK_NOTIFICATIONS == 1 )
        volatile uint32_t ulNotifiedValue; /*與任務通知相關*/
        volatile uint8_t ucNotifyState;
    #endif
    #if( configSUPPORT_STATIC_ALLOCATION == 1 )
        uint8_t ucStaticAllocationFlags; /* 如果堆疊由靜態陣列分配,則設定為pdTRUE,如果堆疊是動態分配的,則設定為pdFALSE*/
    #endif
    #if( INCLUDE_xTaskAbortDelay == 1 )
        uint8_t ucDelayAborted;
    #endif
} tskTCB;
typedef tskTCB TCB_t;

下面我們詳細的介紹這個資料結構的主要成員:

指標pxTopOfStack必須位於結構體的第一項,指向當前堆疊的棧頂,對於向下增長的堆疊,pxTopOfStack總是指向最後一個入棧的專案。

如果使用MPU,xMPUSettings必須位於結構體的第二項,用於MPU設定。

接下來是狀態列表項xStateListItem和事件列表項xEventListItem,我們在上一章介紹列表和列表項的文章中提到過:列表被FreeRTOS排程器使用,用於跟蹤任務,處於就緒、掛起、延時的任務,都會被掛接到各自的列表中。

排程器就是通過把任務TCB中的狀態列表項xStateListItem和事件列表項xEventListItem掛接到不同的列表中來實現上述過程的。

在task.c中,定義了一些靜態列表變數,其中有就緒、阻塞、掛起列表,例如當某個任務處於就緒態時,排程器就將這個任務TCB的xStateListItem列表項掛接到就緒列表。

事件列表項也與之類似,當佇列滿的情況下,任務因入隊操作而阻塞時,就會將事件列表項掛接到佇列的等待入佇列表上。

uxPriority用於儲存任務的優先順序,0為最低優先順序。任務建立時,指定的任務優先順序就被儲存到該變數中。

指標pxStack指向堆疊的起始位置,任務建立時會分配指定數目的任務堆疊,申請堆疊記憶體函數返回的指標就被賦給該變數。

很多剛接觸FreeRTOS的人會分不清指標pxTopOfStack和pxStack的區別,這裡簡單說一下:

pxTopOfStack指向當前堆疊棧頂,隨著進棧出棧,pxTopOfStack指向的位置是會變化的;pxStack指向當前堆疊的起始位置,一經分配後,堆疊起始位置就固定了,不會被改變了。

那麼為什麼需要pxStack變數呢,這是因為隨著任務的執行,堆疊可能會溢位,在堆疊向下增長的系統中,這個變數可用於檢查堆疊是否溢位;

如果在堆疊向上增長的系統中,要想確定堆疊是否溢位,還需要另外一個變數pxEndOfStack來輔助診斷是否堆疊溢位,後面會講到這個變數。

字元陣列pcTaskName用於儲存任務的描述或名字,在任務建立時,由引數指定。

名字的長度由宏configMAX_TASK_NAME_LEN(位於FreeRTOSConfig.h中)指定,包含字串結束標誌。

如果堆疊向上生長(portSTACK_GROWTH > 0),指標pxEndOfStack指向堆疊尾部,用於檢驗堆疊是否溢位。

變數uxCriticalNesting用於儲存臨界區巢狀深度,初始值為0。

接下來兩個變數用於視覺化追蹤,僅當宏configUSE_TRACE_FACILITY(位於FreeRTOSConfig.h中)為1時有效。變數uxTCBNumber儲存一個數值,在建立任務時由核心自動分配數值(通常每建立一個任務,值增加1),每個任務的uxTCBNumber值都不同,主要用於偵錯。

變數uxTaskNumber用於儲存一個特定值,與變數uxTCBNumber不同,uxTaskNumber的數值不是由核心分配的,而是通過API函數vTaskSetTaskNumber()來設定的,數值由函數引數指定。

如果使用互斥量(configUSE_MUTEXES == 1),任務優先順序被臨時提高時,變數uxBasePriority用來儲存任務原來的優先順序。

變數ucStaticAllocationFlags也需要說明一下,我們前面說過任務建立API函數xTaskCreate()只能使用動態記憶體分配的方式建立任務堆疊和任務TCB,如果要使用靜態變數實現任務堆疊和任務TCB就需要使用函數xTaskGenericCreate()來實現。

如果任務堆疊或任務TCB由靜態陣列和靜態變數實現,則將該變數設定為pdTRUE(任務堆疊空間由靜態陣列變數實現時為0x01,任務TCB由靜態變數實現時為0x02,任務堆疊和任務TCB都由靜態變數實現時為0x03),如果堆疊是動態分配的,則將該變數設定為pdFALSE。

到這裡任務TCB的資料結構就講完了,下面我們用一個例子來講述任務建立的過程,為方便起見,假設被建立的任務叫“任務A”,任務函數為vTask_A():

    TaskHandle_t xHandle;
    xTaskCreate(vTask_A,」Task A」,120,NULL,1,&xHandle);

這裡建立了一個任務,任務優先順序為1,由於硬體平臺是32為架構,所以指定了120*4=480位元組的任務堆疊,向任務函數vTask_A()傳遞的引數為空(NULL),任務控制程式碼由變數xHandle儲存。

當這個語句執行後,任務A被建立並加入就緒任務列表,我們這章的主要目的,就是看看這個語句在執行過程中,發生了什麼事情。

1.建立任務堆疊和任務TCB

呼叫函數prvAllocateTCBAndStack()建立任務堆疊和任務TCB。有兩種方式建立任務堆疊和任務TCB,一種是使用動態記憶體分配方法,這樣當任務刪除時,任務堆疊和任務控制塊空間會被釋放,可用於其它任務;

另一種是使用靜態變數來實現,在建立任務前定義好全域性或者靜態堆疊陣列和任務控制塊變數,在呼叫建立任務API函數時,將這兩個變數以引數的形式傳遞給任務建立函數xTaskGenericCreate()。

如果使用預設的xTaskCreate()建立任務函數,則使用動態記憶體分配,因為與靜態記憶體分配有關的引數不可見(在本文一開始我們說過xTaskCreate()其實是一個帶引數的宏定義,真正被執行的函數是xTaskGenericCreate(),參考宏xTaskCreate()的定義可以知道,xTaskCreate()對外隱藏了使用靜態記憶體分配的引數,在呼叫xTaskGenericCreate()時,這些引數被設定為NULL)。

任務堆疊成功分配後,經過對齊的堆疊起始地址被儲存到任務TCB的pxStack欄位。

如果使能堆疊溢位檢查或者使用視覺化追蹤功能,則使用固定值tskSTACK_FILL_BYTE(0xa5)填充堆疊。

函數prvAllocateTCBAndStack()的原始碼去除斷言和不常用的條件編譯後如下所示:

static TCB_t *prvAllocateTCBAndStack( const uint16_t usStackDepth, StackType_t * const puxStackBuffer, TCB_t * const pxTaskBuffer )
{
TCB_t *pxNewTCB;
StackType_t *pxStack;
 
    /* 分配堆疊空間*/
    pxStack = ( StackType_t * ) pvPortMallocAligned( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ), puxStackBuffer );
    if( pxStack != NULL )
    {
        /* 分配TCB空間 */
        pxNewTCB = ( TCB_t * ) pvPortMallocAligned( sizeof( TCB_t ), pxTaskBuffer );
        if( pxNewTCB != NULL )
        {
            /* 將堆疊起始位置存入TCB*/
            pxNewTCB->pxStack = pxStack;
        }
        else
        {
            /* 如果TCB分配失敗,釋放之前申請的堆疊空間 */
            if( puxStackBuffer == NULL )
            {
                vPortFree( pxStack );
            }
        }
    }
    else
    {
        pxNewTCB = NULL;
    }
    if( pxNewTCB != NULL )
    {
        /* 如果需要,使用固定值填充堆疊 */
        #if( ( configCHECK_FOR_STACK_OVERFLOW> 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark== 1 ) )
        {
            /* 僅用於偵錯 */
            ( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) usStackDepth * sizeof( StackType_t ) );
        }
        #endif
    }
    return pxNewTCB;
}

2.初始化任務TCB必要的欄位

呼叫函數prvInitialiseTCBVariables()初始化任務TCB必要的欄位。在呼叫建立任務API函數xTaskCreate()時,引數pcName(任務描述)、uxPriority(任務優先順序)都會被寫入任務TCB相應的欄位,TCB欄位中的xStateListItem和xEventListItem列表項也會被初始化,初始化後的列表項如圖2-1所示。

在圖2-1中,列表項xEventListItem的成員列表項值xItemValue被初始為4,這是因為我在應用中設定的最大優先順序數目(configMAX_PRIORITIES)為5,而xEventListItem. xItemValue等於configMAX_PRIORITIES減去任務A的優先順序(為1),即5-1=4。這一點很重要,在這裡xItemValue不是直接儲存任務優先順序,而是儲存優先順序的補數,這意味著xItemValue的值越大,對應的任務優先順序越小。

FreeRTOS核心使用vListInsert函數(詳細見FreeRTOS進階列表和列表項範例分析)將事件列表項插入到一個列表,這個函數根據xItemValue的值的大小順序來進行插入操作。

使用宏listGET_OWNER_OF_HEAD_ENTRY獲得列表中的第一個列表項的xItemValue值總是最小,也就是優先順序最高的任務!

圖2-1:初始化狀態和事件列表項

此外,TCB其它的一些欄位也被初始化,比如臨界區巢狀次數、執行時間計數器、任務通知值、任務通知狀態等,函數prvInitialiseTCBVariables()的原始碼如下所示:

static void prvInitialiseTCBVariables( TCB_t * const pxTCB, const char * const pcName, UBaseType_t uxPriority,   
                              const MemoryRegion_t * const xRegions, const uint16_t usStackDepth )
{
UBaseType_t x;
    /* 將任務描述存入TCB */
    for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
    {
        pxTCB->pcTaskName[ x ] = pcName[ x ];
        if( pcName[ x ] == 0x00 )
        {
            break;
        }
    }
    /* 確保字串有結束 */
    pxTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '';
 
    /* 調整優先順序,宏configMAX_PRIORITIES的值在FreeRTOSConfig.h中設定 */
    if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
    {
        uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
    }
    pxTCB->uxPriority = uxPriority;
    #if ( configUSE_MUTEXES == 1 )              /*使用互斥量*/
    {  
        pxTCB->uxBasePriority = uxPriority;
        pxTCB->uxMutexesHeld = 0;
    }
    #endif /* configUSE_MUTEXES */
    /*初始化列表項*/
    vListInitialiseItem( &( pxTCB->xStateListItem ) );
    vListInitialiseItem( &( pxTCB->xEventListItem ) );
    /* 設定列表項xStateListItem的成員pvOwner指向當前任務控制塊 */
    listSET_LIST_ITEM_OWNER( &( pxTCB->xStateListItem ), pxTCB );
    /* 設定列表項xEventListItem的成員xItemValue*/
    listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
    /* 設定列表項xEventListItem的成員pvOwner指向當前任務控制塊 */
    listSET_LIST_ITEM_OWNER( &( pxTCB->xEventListItem ), pxTCB );
    #if ( portCRITICAL_NESTING_IN_TCB ==1 )    /*使能臨界區巢狀功能*/
    {  
        pxTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
    }
    #endif /* portCRITICAL_NESTING_IN_TCB */
    #if ( configUSE_APPLICATION_TASK_TAG == 1 ) /*使能任務標籤功能*/
    {  
        pxTCB->pxTaskTag = NULL;
    }
    #endif /* configUSE_APPLICATION_TASK_TAG */
    #if ( configGENERATE_RUN_TIME_STATS == 1 )  /*使能事件統計功能*/
    {
        pxTCB->ulRunTimeCounter = 0UL;
    }
    #endif /* configGENERATE_RUN_TIME_STATS */
    #if ( portUSING_MPU_WRAPPERS == 1 )         /*使用MPU功能*/
    {
        vPortStoreTaskMPUSettings( &( pxTCB->xMPUSettings ), xRegions, pxTCB->pxStack, usStackDepth );
    }
    #else /* portUSING_MPU_WRAPPERS */
    {
        ( void ) xRegions;
        ( void ) usStackDepth;
    }
    #endif /* portUSING_MPU_WRAPPERS */
    #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )/*使能執行緒本地儲存指標*/
    {
        for( x = 0; x < ( UBaseType_t )configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
        {
            pxTCB->pvThreadLocalStoragePointers[ x ] = NULL;
        }
    }
    #endif
    #if ( configUSE_TASK_NOTIFICATIONS == 1 )   /*使能任務通知功能*/
    {
        pxTCB->ulNotifiedValue = 0;
        pxTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
    }
    #endif
    #if ( configUSE_NEWLIB_REENTRANT == 1 )     /*使用Newlib*/
    {
        _REENT_INIT_PTR( ( &( pxTCB->xNewLib_reent ) ) );
    }
    #endif
    #if( INCLUDE_xTaskAbortDelay == 1 )
    {
        pxTCB->ucDelayAborted = pdFALSE;
    }
    #endif
}

3.初始化任務堆疊

呼叫函數pxPortInitialiseStack()初始化任務堆疊,並將最新的棧頂指標賦值給任務TCB的pxTopOfStack欄位。

呼叫函數pxPortInitialiseStack()後,相當於執行了一次系統節拍時鐘中斷:將一些重要暫存器入棧。雖然任務還沒開始執行,也並沒有中斷髮生,但看上去就像暫存器已經被入棧了,並且部分堆疊值被修改成了我們需要的已知值。

對於不同的硬體架構,入棧的暫存器也不相同,所以我們看到這個函數是由移植層提供的。對於Cortex-M3架構,需要依次入棧xPSR、PC、LR、R12、R3~R0、R11~R4,假設堆疊是向下生長的,初始化後的堆疊如圖3-1所示。

在圖3-1中我們看到暫存器xPSR被初始為0x01000000,其中bit24被置1,表示使用Thumb指令;

暫存器PC被初始化為任務函數指標vTask_A,這樣當某次工作切換後,任務A獲得CPU控制權,任務函數vTask_A被出棧到PC暫存器,之後會執行任務A的程式碼;

LR暫存器初始化為函數指標prvTaskExitError,這是由移植層提供的一個出錯處理常式。

當中斷髮生時,LR被設定成中斷要返回的地址,但是每個任務都是一個死迴圈,正常情況下不應該退出任務函數,所以一旦從任務函數退出,說明那裡出錯了,這個時候會呼叫暫存器LR指向的函數來處理這個錯誤,即prvTaskExitError;

根據ATPCS(ARM-Thumb過程呼叫標準),我們知道子函數呼叫通過暫存器R0~R3傳遞引數,在文章的最開始講xTaskCreate()函數時,提到這個函數有一個空指標型別的引數pvParameters,當任務建立時,它作為一個引數傳遞給任務,所以這個引數被儲存到R0中,用來向任務傳遞引數。

任務TCB結構體成員pxTopOfStack表示當前堆疊的棧頂,它指向最後一個入棧的專案,所以在圖中它指向R4,TCB結構體另外一個成員pxStack表示堆疊的起始位置,所以在圖中它指向堆疊的最開始處。

圖3-1:初始化任務堆疊

4.進入臨界區

呼叫taskENTER_CRITICAL()進入臨界區,這是一個宏定義,最終進入臨界區的程式碼由移植層提供。

5.當前任務數量增加1

在tasks.c中 ,定義了一些靜態私有變數,用來跟蹤任務的數量或者狀態等等,其中變數uxCurrentNumberOfTasks表示當前任務的總數量,每建立一個任務,這個變數都會增加1。

6.為第一次執行做必要的初始化

如果這是第一個任務(uxCurrentNumberOfTasks等於1),則呼叫函數prvInitialiseTaskLists()初始化任務列表。FreeRTOS使用列表來跟蹤任務,在tasks.c中,定義了靜態型別的列表變數:

PRIVILEGED_DATAstatic List_t pxReadyTasksLists[ configMAX_PRIORITIES ];/*按照優先順序排序的就緒態任務*/
PRIVILEGED_DATAstatic List_t xDelayedTaskList1;                        /*延時的任務 */
PRIVILEGED_DATAstatic List_t xDelayedTaskList2;                        /*延時的任務 */
PRIVILEGED_DATAstatic List_t xPendingReadyList;                        /*任務已就緒,但排程器被掛起 */
#if (INCLUDE_vTaskDelete == 1 )
    PRIVILEGED_DATA static List_t xTasksWaitingTermination;             /*任務已經被刪除,但記憶體尚未釋放*/
#endif
#if (INCLUDE_vTaskSuspend == 1 )
    PRIVILEGED_DATA static List_t xSuspendedTaskList;                   /*當前掛起的任務*/
#endif

現在這些列表都要進行初始化,會呼叫API函數vListInitialise()初始化列表,這個函數在FreeRTOS列表和列表項中講過,每個列表的初始化方式都是相同的,以就緒態列表pxReadyTasksLists[0]為例,初始化後如圖6-1所示:

圖6-1:初始化後的列表

函數prvInitialiseTaskLists()的原始碼如下所示:

static void prvInitialiseTaskLists( void )
{
UBaseType_tuxPriority;
    for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
    {
        vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
    }
    vListInitialise( &xDelayedTaskList1 );
    vListInitialise( &xDelayedTaskList2 );
    vListInitialise( &xPendingReadyList );
    #if ( INCLUDE_vTaskDelete == 1 )
    {
        vListInitialise( &xTasksWaitingTermination );
    }
    #endif /* INCLUDE_vTaskDelete */
 
    #if ( INCLUDE_vTaskSuspend == 1 )
    {
        vListInitialise( &xSuspendedTaskList );
    }
    #endif /* INCLUDE_vTaskSuspend */
    /* Start with pxDelayedTaskList using list1 and the pxOverflowDelayedTaskListusing list2. */
    pxDelayedTaskList = &xDelayedTaskList1;
    pxOverflowDelayedTaskList = &xDelayedTaskList2;
}

7.更新當前正在執行的任務TCB指標

tasks.c中定義了一個任務TCB指標型變數:

PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB= NULL;

這是一個全域性變數,在tasks.c中只定義了這一個全域性變數。這個變數用來指向當前正在執行的任務TCB,我們需要多瞭解一下這個變數。

FreeRTOS的核心是確保處於優先順序最高的就緒任務獲得CPU執行權。在下一章講述工作切換時會知道,工作切換就是找到優先順序最高的就緒任務,而找出的這個最高優先順序任務的TCB,就被賦給變數pxCurrentTCB。

如果排程器還沒有準備好(程式剛開始執行時,可能會先建立幾個任務,之後才會啟動排程器),並且新建立的任務優先順序大於變數pxCurrentTCB指向的任務優先順序,則設定pxCurrentTCB指向當前新建立的任務TCB(確保pxCurrentTCB指向優先順序最高的就緒任務)。

if( xSchedulerRunning == pdFALSE )
{
    if( pxCurrentTCB->uxPriority <= uxPriority )
    {
        pxCurrentTCB = pxNewTCB;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

8.將新建立的任務加入就緒列表陣列

呼叫prvAddTaskToReadyList(pxNewTCB)將建立的任務TCB加入到就緒列表陣列中,任務的優先順序確定了加入到就緒列表陣列的哪個下標。比如我們新建立的任務優先順序為1,則這個任務被加入到列表pxReadyTasksLists[1]中。

prvAddTaskToReadyList()其實是一個宏,由一系列語句組成,去除其中的跟蹤宏外,這個宏定義如下所示:

#defineprvAddTaskToReadyList( pxTCB )                        
    taskRECORD_READY_PRIORITY( ( pxTCB)->uxPriority );       
    vListInsertEnd( &( pxReadyTasksLists[ (pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) );

宏taskRECORD_READY_PRIORITY()用來更新變數uxTopReadyPriority,這個變數在tasks.c中定義為靜態變數,記錄處於就緒態的最高任務優先順序。這個變數參與了FreeRTOS的最核心程式碼:確保處於優先順序最高的就緒任務獲得CPU執行權。它在這裡參與如何最快的找到優先順序最高的就緒任務。為了最快,不同的架構會各顯神通,一些架構還有特殊指令可用,所以這個宏由移植層提供。我們會在下一章介紹工作切換時,以Cortex-M3架構為例,詳細介紹如何最快的找到優先順序最高的就緒任務。

函數vListInsertEnd()將列表項插入到列表末端,在FreeRTOS進階列表和列表項範例分析中已經提到過,這裡會結合著例子再看一下這個函數。

從前面我們直到,在呼叫函數vListInsertEnd()之前,就緒列表pxReadyTasksLists[1]和任務TCB的狀態列表項xStateListItem都已經初始化好了,見圖6-1和圖2-1,為了方便檢視,我們將這兩幅圖合成一副,見圖8-1。

圖8-1:初始化後的列表和列表項

呼叫vListInsertEnd(a,b)會將列表項b,插入到列表a的後面,函數執行完畢後,列表和列表項的關係如圖8-2所示。

圖8-2:插入一個列表項後的列表

在此基礎上,假設又建立了任務B,任務A和任務B優先順序相同,都為1。和任務A一樣,任務B也有它自己的任務TCB,其中的狀態列表項欄位xStateListItem也要插入到列表pxReadyTasksLists[1]中,新的列表和列表項如圖8-3所示。

圖8-3:相同優先順序就緒列表掛接兩個列表項

9.退出臨界區

呼叫taskEXIT_CRITICAL()退出臨界區,這是一個宏定義,最終退出臨界區的程式碼由移植層提供。

10.執行上下文切換

如果上面的步驟都正確執行,並且排程器也開始工作,則判斷當前任務的優先順序是否大於新建立的任務優先順序。如果新建立的任務優先順序更高,則呼叫taskYIELD_IF_USING_PREEMPTION()強制進行一次上下文切換,切換後,新建立的任務將獲得CPU控制權,精簡後的程式碼如下所示。

 if( xReturn == pdPASS )
    {
        if( xSchedulerRunning != pdFALSE )
        {
            /* 如果新建立的任務優先順序大於當前任務優先順序,則新建立的任務應該被立即執行。*/
            if(pxCurrentTCB->uxPriority < uxPriority )
            {
                taskYIELD_IF_USING_PREEMPTION();
            }
        }
    }

以上就是FreeRTOS進階之任務建立完全解析的詳細內容,更多關於FreeRTOS進階任務建立分析的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com