1. 程式人生 > >FreeRTOS(13)---FreeRTOS 列表和列表項

FreeRTOS(13)---FreeRTOS 列表和列表項

FreeRTOS 列表和列表項

FreeRTOS核心排程大量使用了列表(list)和列表項(list item)資料結構。我們如果想一探FreeRTOS背後的執行機制,首先遇到的攔路虎就是列表和列表項。對於FreeRTOS核心來說,列表就是它最基礎的部分。我們在這一章集中講解列表和列表項的結構以及操作函式,在下一章講解任務建立時,會用到本章的知識點。

列表被FreeRTOS排程器使用,用於跟蹤任務,處於就緒、掛起、延時的任務,都會被掛接到各自的列表中。使用者程式如果有需要,也可以使用列表。

FreeRTOS列表使用指標指向列表項。一個列表(list)下面可能有很多個列表項(list item),每個列表項都有一個指標指向列表。如圖1-1所示。

在這裡插入圖片描述

圖1-1:列表與列表項

列表項有兩種形式,全功能版的列表項xLIST_ITEM和迷你版的列表項xMINI_LIST_ITEM。我們來看一下它們具體的定義,先看全功能版。

struct xLIST_ITEM
{
     listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE           /*用於檢測列表項資料是否完整*/
     configLIST_VOLATILETickType_t xItemValue;           /*列表項值*/
     struct xLIST_ITEM * configLIST_VOLATILE pxNext;      /*指向列表中下一個列表項*/
     struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;  /*指向列表中上一個列表項*/
     void * pvOwner;                                     /*指向一個任務TCB*/
     void * configLIST_VOLATILE pvContainer;             /*指向包含該列表項的列表 */
     listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE          /*用於檢測列表項資料是否完整*/
};
typedef struct xLIST_ITEM ListItem_t;

巨集listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE和listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE用於檢查列表項資料是否完整,在projdefs.h中,如果將巨集configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES設定為1,則使能列表項資料完整性檢查,則巨集listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE和listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE會被兩個已知的數值代替。

xItemValue是列表項值,通常是一個被跟蹤的任務優先順序或是一個排程事件的計數器值。如果任務因為等待從佇列取資料而進入阻塞狀態,則任務的事件列表項的列表項值儲存任務優先順序有關資訊,狀態列表項的列表項值儲存阻塞時間有關的資訊。這個變數被configLIST_VOLATILE修飾,configLIST_VOLATILE被對映成C語言關鍵字volatile,表明這個變數是“易變的”,告訴編譯器不得對這個變數進行程式碼優化,因為列表項的成員可能會在中斷服務程式中被更新。關於volatile關鍵字,如果不是熟悉的話,可以參考我的博文《編寫優質嵌入式C程式》第3.2.4節。

pxNext和pxPrevious是列表項型別指標,用來指向列表中下一個和上一個列表項,通過這兩個指標,列表項之間可以形成類似雙向連結串列結構。

指標pvOwner通常指向一個任務TCB。

指標pvContainer指向包含該列表項的列表。

迷你版的列表項xMINI_LIST_ITEM是全功能版列表項xLIST_ITEM的一個子集,定義如下所示:

struct xMINI_LIST_ITEM
{
     listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE           /*用於檢測列表項資料是否完整*/
     configLIST_VOLATILE TickType_t xItemValue;
     struct xLIST_ITEM * configLIST_VOLATILE pxNext;
     struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

既然有了全功能版的列表項,為什麼還要宣告迷你版的列表項呢?這是因為列表結構體需要一個列表項成員,但又不需要列表項中的所有欄位,所以才有了迷你版列表項。列表結構體定義為:

typedef struct xLIST
{
     listFIRST_LIST_INTEGRITY_CHECK_VALUE                        /*用於檢測列表項資料是否完整*/
     configLIST_VOLATILE UBaseType_t uxNumberOfItems;
     ListItem_t * configLIST_VOLATILE pxIndex;                   /*用於遍歷列表*/
     MiniListItem_t xListEnd;                                    /*列表項*/
     listSECOND_LIST_INTEGRITY_CHECK_VALUE                       /*用於檢測列表項資料是否完整*/
}List_t;

和列表項定義相同,巨集listFIRST_LIST_INTEGRITY_CHECK_VALUE和listSECOND_LIST_INTEGRITY_CHECK_VALUE用於檢查列表項資料是否完整,在projdefs.h中,如果將巨集configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES設定為1,則使能列表項資料完整性檢查,則巨集listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE和listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE會被兩個已知的數值代替。

uxNumberOfItems表示該列表中掛接的列表項數目,0表示列表為空。

列表項型別指標用於遍歷列表,列表初始化後,這個指標指向&xListEnd。通過巨集listGET_OWNER_OF_NEXT_ENTRY()來獲取列表中的下一個列表項。

列表項xListEnd用於標記列表結束。xListEnd.xItemValue被初始化為一個常數,其值與硬體架構相關,為0xFFFF(16位架構)或者0xFFFFFFFF(32位架構)。

下面我們看一下列表操作。FreeROTS提供了幾個API函式,用於初始化列表和列表項以及列表項插入操作。

初始化列表

列表結構體中包含一個列表項成員,主要用於標記列表結束。初始化列表就是把這個列表項插入到列表中。

void vListInitialise( List_t * const pxList )
{
     /*列表索引指向列表項*/
     pxList->pxIndex = ( ListItem_t * )&( pxList->xListEnd );                  
     /* 設定為最大可能值 */
     pxList->xListEnd.xItemValue =portMAX_DELAY;
 
     /* 列表項xListEnd的pxNext和pxPrevious指標指向了它自己 */
     pxList->xListEnd.pxNext = (ListItem_t * ) &( pxList->xListEnd );
     pxList->xListEnd.pxPrevious= ( ListItem_t * ) &( pxList->xListEnd );
     pxList->uxNumberOfItems = ( UBaseType_t) 0U;
 
     /* 設定為已知值,用於檢測列表資料是否完整*/
     listSET_LIST_INTEGRITY_CHECK_1_VALUE(pxList );
     listSET_LIST_INTEGRITY_CHECK_2_VALUE(pxList );
}

如果巨集configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES設定為1,則使能列表項資料完整性檢查,則巨集listSET_LIST_INTEGRITY_CHECK_1_VALUE()和listSET_LIST_INTEGRITY_CHECK_2_VALUE被一個已知值代替,預設為0x5a5a(16位架構)或者0x5a5a5a5a(32位架構)。

假設禁止列表資料完整性檢查,初始化後的列表如圖1-2所示,uxNumberOfItems被初始化為0,xListEnd.xItemValue初始化為0xffffffff,pxIndex、xListEnd.pxNext和xListEnd.pxPrevious初始化為指向列表項xListEnd。

在這裡插入圖片描述

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

初始化列表項

列表項的初始比較簡單,只要確保列表項不在任何列表中即可。

void vListInitialiseItem( ListItem_t * const pxItem )
{
     pxItem->pvContainer = NULL;
 
     /*設定為已知值,用於檢測列表項資料是否完整*/
     listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE(pxItem );
     listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE(pxItem );
}

如果巨集configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES設定為1,則使能列表項資料完整性檢查,則巨集listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE和listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE會被兩個已知的數值代替,預設為0x5a5a(16位架構)或者0x5a5a5a5a(32位架構)。

假設禁止列表項資料完整性檢查,初始化後的列表項如圖1-3所示。僅是將指標pvContainer設定為空指標,該指標用於指向包含該列表項的列表,這裡設定為NULL表示這個列表項不屬於任何列表。 在這裡插入圖片描述

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

將列表項插入到列表中,列表項所在的位置取決於列表項的列表項值(xItemValue)。

每個列表項物件都有一個列表項值(xItemValue),通常是一個被跟蹤的任務優先順序或是一個排程事件的計數器值。呼叫API函式vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem)可以將pxNewListItem指向的列表項插入到pxList指向的列表中,列表項在列表的位置由pxNewListItem->xItemValue決定,按照降序排列。

void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
 
         /* 檢查列表和列表項資料的完整性,僅當configASSERT()定義時有效。*/
         listTEST_LIST_INTEGRITY( pxList );
         listTEST_LIST_ITEM_INTEGRITY(pxNewListItem );
 
         /*將新的列表項插入到列表,根據xItemValue的值降序插入列表。*/
         if( xValueOfInsertion == portMAX_DELAY)
         {
                   pxIterator =pxList->xListEnd.pxPrevious;
         }
         else
         {
                   for( pxIterator = (ListItem_t * ) &( pxList->xListEnd );pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator =pxIterator->pxNext )
                   {
                            /* 這裡為空 */
                   }
         }
 
         pxNewListItem->pxNext =pxIterator->pxNext;
         pxNewListItem->pxNext->pxPrevious= pxNewListItem;
         pxNewListItem->pxPrevious =pxIterator;
         pxIterator->pxNext = pxNewListItem;
 
         pxNewListItem->pvContainer = ( void* ) pxList;
 
         ( pxList->uxNumberOfItems )++;
}

根據xItemValue的值將新的列表項插入到列表。如果列表中存在與新列表項xItemValue值相同的列表項,則新插入的列表項位於它之後。如果列表項的xItemValue值等於portMAX_DELAY(列表結束標記,我們在講列表資料結構時,說到每個列表資料結構體中都有一個列表項成員xListEnd,用於標記列表結束。xListEnd.xItemValue被初始化為一個常數,其值與硬體架構相關,為0xFFFF或者0xFFFFFFFF。這個常數在移植層定義,即巨集portMAX_DELAY),則表示到達了列表結束位置。

我們用圖示的方法來講解這個函式,我們假設一個列表項值(xItemValue)為32的列表項插入到如圖1-2所示的初始化後的列表中,呼叫vListInsert()函式後,列表和列表項的關係如圖1-4所示。列表項xListItem_1的成員指標pxNext和pxPrevious都指向了xListEnd,而xListEnd的成員指標pxNext和pxPrevious都指向了列表項xListItem_1;列表項xListItem_1的成員指標pvContainer指向了列表xList_1;列表成員uxNumberOfItems為1。

在這裡插入圖片描述

圖1-4:將列表項插入到列表

在此基礎上,如果再將一個列表項值(xItemValue)為40的列表項插入到列表中,呼叫vListInsert()函式後,列表和列表項的關係如圖1-5所示。 在這裡插入圖片描述

圖1-5:將列表項插入到列表

將列表項插入到列表末端

第3節講的API插入函式是根據列表項中的列表項值(xItemValue)來決定插入位置的,本節所講的API函式vListInsertEnd()是簡單的將列表項插入到列表的末端。在下一章任務建立分析的文章中,將會遇到這個API函式,到時再以圖示的形式分析這個函式,現在給出這個函式的原始碼。

void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t* const pxIndex = pxList->pxIndex;
 
         /*檢查列表和列表項資料的完整性,僅當configASSERT()定義時有效。*/
         listTEST_LIST_INTEGRITY( pxList );
         listTEST_LIST_ITEM_INTEGRITY(pxNewListItem );
 
         /*向列表中插入新的列表項*/
         pxNewListItem->pxNext = pxIndex;
         pxNewListItem->pxPrevious =pxIndex->pxPrevious;
 
         mtCOVERAGE_TEST_DELAY();
 
         pxIndex->pxPrevious->pxNext =pxNewListItem;
         pxIndex->pxPrevious = pxNewListItem;
 
         pxNewListItem->pvContainer = ( void* ) pxList;
 
         ( pxList->uxNumberOfItems )++;
}