從0開始學FreeRTOS-(列表&列表項)-6

FreeRTOS列表&列表項的源碼解讀

第一次看列表與列表項的時候,感受很像是鏈表,雖然我本身的鏈表也不太會,可是就是感受很像。數據結構

在FreeRTOS中,列表與列表項使用得很是多,是FreeRTOS的一個數據結構,學習過數據結構的同窗都知道,數據結構能使咱們處理數據更加方便快速,能快速找到數據,在FreeRTOS中,這種列表與列表項更是必不可少的,能讓咱們的系統跑起來更加流暢迅速。函數

言歸正傳,FreeRTOS中使用了大量的列表(List)與列表項(Listitem),在FreeRTOS調度器中,就是用到這些來跟着任務,瞭解任務的狀態,處於掛起、阻塞態、仍是就緒態亦或者是運行態。這些信息都會在各自任務的列表中獲得。學習

看任務控制塊(tskTaskControlBlock)中的兩個列表項:spa

ListItem_t xStateListItem; /* <任務的狀態列表項目引用的列表表示該任務的狀態(就緒,已阻止,暫停)。*/
ListItem_t xEventListItem; /* <用於從事件列表中引用任務。*/

一個是狀態的列表項,一個是事件列表項。他們在建立任務就會被初始化,列表項的初始化是根據實際須要來初始化的,下面會說。操作系統

FreeRTOS列表&列表項的結構體

既然知道列表與列表項的重要性,那麼咱們來解讀FreeRTOS中的list.c與list.h的源碼吧。從頭文件lsit.h開始,看到定義了一些結構體:3d

struct xLIST_ITEM
{
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /* <若是configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES設置爲1,則設置爲已知值。*/
    configLIST_VOLATILE TickType_t xItemValue; /* <正在列出的值。在大多數狀況下,這用於按降序對列表進行排序。 */
    struct xLIST_ITEM * configLIST_VOLATILE pxNext; /* <指向列表中下一個ListItem_t的指針。 */
    struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /* <指向列表中前一個ListItem_t的指針。 */
    void * pvOwner; /* <指向包含列表項目的對象(一般是TCB)的指針。所以,包含列表項目的對象與列表項目自己之間存在雙向連接。 */
    void * configLIST_VOLATILE pvContainer; /* <指向此列表項目所在列表的指針(若是有)。 */
    listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE /* <若是configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES設置爲1,則設置爲已知值。*/
};
typedef struct xLIST_ITEM ListItem_t; /* 因爲某種緣由,lint但願將其做爲兩個單獨的定義。 */

列表項結構體的一些注意的地方:指針

xItemValue 用於列表項的排序,相似1—2—3—4code

pxNext 指向下一個列表項的指針
pxPrevious 指向上(前)一個列表項的指針對象

這兩個指針實現了相似雙向鏈表的功能blog

pvOwner 指向包含列表項目的對象(一般是任務控制塊TCB)的指針。所以,包含列表項目的對象與列表項目自己之間存在雙向連接。

pvContainer 記錄了該列表項屬於哪一個列表,說白點就是這個兒子是誰生的。。。

        
同時定義了一個MINI的列表項的結構體,MINI列表項是刪減版的列表項,由於不少時候不須要徹底版的列表項。就不用浪費那麼多內存空間了,這或許就是FreeRTOS是輕量級操做系統的緣由吧,能省一點是一點。MINI列表項:

struct xMINI_LIST_ITEM
{
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE           /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
    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 /* <若是configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES設置爲1,則設置爲已知值。*/
    configLIST_VOLATILE UBaseType_t uxNumberOfItems;
    ListItem_t * configLIST_VOLATILE pxIndex; /* <用於遍歷列表。 指向由listGET_OWNER_OF_NEXT_ENTRY()調用返回的後一個列表項。*/
    MiniListItem_t xListEnd; /* <List item包含最大可能的項目值,這意味着它始終在列表的末尾,所以用做標記。*/
    listSECOND_LIST_INTEGRITY_CHECK_VALUE /* <若是configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES設置爲1,則設置爲已知值。*/
} List_t;

列表的結構體中值得注意的是:
uxNumberOfItems 是用來記錄列表中列表項的數量的,就是記錄父親有多少個兒子,固然女兒也行~。

pxIndex 是索引編號,用來遍歷列表的,調用宏listGET_OWNER_OF_NEXT_ENTRY()以後索引就會指向返回當前列表項的下一個列表項。

xListEnd 指向的是最後一個列表項,而且這個列表項是MiniListItem屬性的,是一個迷你列表項。

列表的初始化

函數:

void vListInitialise( List_t * const pxList )
{
     pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );           /*lint The mini list structure is used as the list end to save RAM.  This is checked and valid. */
     pxList->xListEnd.xItemValue = portMAX_DELAY;
     pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );   /*lint The mini list structure is used as the list end to save RAM.  This is checked and valid. */
     pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );/*lint The mini list structure is used as the list end to save RAM.  This is checked and valid. */
     pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
     listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
     listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}

將列表的索引指向列表中的xListEnd,也就是末尾的列表項(迷你列表項)

列表項的xItemValue數值爲portMAX_DELAY,也就是0xffffffffUL,若是在16位處理器中則爲0xffff。

列表項的pxNext與pxPrevious這兩個指針都指向本身自己xListEnd。

初始化完成的時候列表項的數目爲0個。由於還沒添加列表項嘛~。

列表項的初始化

函數:

void vListInitialiseItem( ListItem_t * const pxItem )
{
    /* Make sure the list item is not recorded as being on a list. */
    pxItem->pvContainer = NULL;
    /* Write known values into the list item if
    configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
    listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
    listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}

只須要讓列表項的pvContainer指針指向NULL便可,這樣子就使得列表項不屬於任何一個列表,由於列表項的初始化是要根據實際的狀況來進行初始化的。

例如任務建立時用到的一些列表項初始化:

pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
pxNewTCB->uxPriority = uxPriority;
pxNewTCB->uxBasePriority = uxPriority;
pxNewTCB->uxMutexesHeld = 0;

vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
vListInitialiseItem( &( pxNewTCB->xEventListItem ) );

 或者是在定時器相關的初始化中:

pxNewTimer->pcTimerName = pcTimerName;
pxNewTimer->xTimerPeriodInTicks = xTimerPeriodInTicks;
pxNewTimer->uxAutoReload = uxAutoReload;
pxNewTimer->pvTimerID = pvTimerID;
pxNewTimer->pxCallbackFunction = pxCallbackFunction;

vListInitialiseItem( &( pxNewTimer->xTimerListItem ) );

列表項的末尾插入

函數:

void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
    ListItem_t * const pxIndex = pxList->pxIndex;
    listTEST_LIST_INTEGRITY( pxList );
    listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
    listGET_OWNER_OF_NEXT_ENTRY(). */
    pxNewListItem->pxNext = pxIndex;    //  1 
    pxNewListItem->pxPrevious = pxIndex->pxPrevious;    //  2
     /* Only used during decision coverage testing. */
    mtCOVERAGE_TEST_DELAY();
    pxIndex->pxPrevious->pxNext = pxNewListItem;        //  3 
    pxIndex->pxPrevious = pxNewListItem;                //  4
    /* Remember which list the item is in. */
    pxNewListItem->pvContainer = ( void * ) pxList;
    ( pxList->uxNumberOfItems )++;
}

傳入的參數:

  • pxList:列表項要插入的列表。
  • pxNewListItem:要插入的列表項是什麼。

從末尾插入,那就要先知道哪裏是頭咯,咱們在列表中的成員pxIndex就是用來遍歷列表項的啊,那它指向的地方就是列表項的頭,那麼既然FreeRTOS中的列表很像數據結構中的雙向鏈表,那麼,咱們能夠把它當作一個環,是首尾相連的,那麼函數中說的末尾,就是列表項頭的前一個,很顯然其結構圖應該是下圖這樣子的(初始化結束後pxIndex指向了xListEnd):

爲何是這樣子的呢,一句句代碼來解釋:

一開始:

ListItem_t * const pxIndex = pxList->pxIndex;

保存了一開始的索引列表項(xListEnd)的指向。

pxNewListItem->pxNext = pxIndex;         //  1

新列表項的下一個指向爲索引列表項,也就是綠色的箭頭。

pxNewListItem->pxPrevious = pxIndex->pxPrevious;      //  2

剛開始咱們初始化完成的時候pxIndex->pxPrevious的指向爲本身xListEnd,那麼xNewListItem->pxPrevious的指向爲xListEnd。如2紫色的箭頭。

pxIndex->pxPrevious->pxNext = pxNewListItem;             //  3

索引列表項(xListEnd)的上一個列表項仍是本身,那麼本身的下一個列表項指向就是指向了pxNewListItem。

pxIndex->pxPrevious = pxNewListItem;                              //  4

這句就很容易理解啦。如圖的4橙色的箭頭。

插入完畢的時候標記一下新的列表項插入了哪一個列表,而且將uxNumberOfItems進行加一,以表示多了一個列表項。

爲何源碼要這樣子寫呢?由於這只是兩個列表項,一個列表含有多個列表項,那麼這段代碼的通用性就很強了。不管本來列表中有多少個列表項,也不管pxIndex指向哪一個列表項!

看看是否是按照源碼中那樣插入呢?

列表項的插入

源碼:

void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
    listTEST_LIST_INTEGRITY( pxList );
    listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
    if( xValueOfInsertion == portMAX_DELAY )
    {
        pxIterator = pxList->xListEnd.pxPrevious;
    }
    else
    {
        for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) /*lint !e826 !e740 The mini list structure is used as the list end to save RAM.  This is checked and valid. */
        {
            /* There is nothing to do here, just iterating to the wanted
            insertion position. */
        }
    }
    pxNewListItem->pxNext = pxIterator->pxNext;
    pxNewListItem->pxNext->pxPrevious = pxNewListItem;
    pxNewListItem->pxPrevious = pxIterator;
    pxIterator->pxNext = pxNewListItem;
    /* Remember which list the item is in.  This allows fast removal of the
    item later. */
    pxNewListItem->pvContainer = ( void * ) pxList;
    ( pxList->uxNumberOfItems )++;
}

傳入的參數:

  • pxList:列表項要插入的列表。
  • pxNewListItem:要插入的列表項是什麼。

pxList決定了插入哪一個列表,pxNewListItem中的xItemValue值決定了列表項插入列表的位置。

ListItem_t *pxIterator;  
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;

定義一個輔助的列表項pxIterator,用來迭代找出插入新列表項的位置,而且保存獲取要插入的列表項pxNewListItem的xItemValue。

若是打開了列表項完整性檢查,就要用戶實現configASSERT(),源碼中有說明。

既然是要插入列表項,那麼確定是要知道列表項的位置了,若是新插入列表項的xItemValue是最大的話(portMAX_DELAY),就直接插入列表項的末尾。不然就須要比較列表中各個列表項的xItemValue的大小來進行排列。而後得出新列表項插入的位置。

for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext )

上面源碼就是實現比較的過程。

與上面的從列表項末尾插入的源碼同樣,FreeRTOS的代碼通用性很強,邏輯思惟也很強。

若是列表中列表項的數量爲0,那麼插入的列表項就是在初始化列表項的後面。以下圖所示:

過程分析:
新列表項的pxNext指向pxIterator->pxNext,也就是指向了xListEnd(pxIterator)。

pxNewListItem->pxNext = pxIterator->pxNext;

而xListEnd(pxIterator)的pxPrevious指向則爲pxNewListItem。

pxNewListItem->pxNext->pxPrevious = pxNewListItem;

新列表項的(pxPrevious)指針指向xListEnd(pxIterator)

pxIterator 的 pxNext 指向了新列表項

pxNewListItem->pxPrevious = pxIterator;
pxIterator->pxNext = pxNewListItem;

與從末尾插入列表項實際上是同樣的,前提是當前列表中列表項的數目爲0。

假如列表項中已經有了元素呢,過程又是不同的了。原來的列表是下圖這樣子的:


假設插入的列表項的xItemValue是2,而原有的列表項的xItemValue值是3,那麼,按照源碼,咱們插入的列表項是在中間了。而pxIterator則是①號列表項。

插入後的效果:

分析一下插入的過程:

新的列表項的pxNext指向的是pxIterator->pxNext,也就是③號列表項。由於一開始pxIterator->pxNext=指向的就是③號列表項!!

pxNewListItem->pxNext = pxIterator->pxNext;

而pxNewListItem->pxNext 即③號列表項的指向上一個列表項指針(pxPrevious)的則指向新插入的列表項,也就是②號列表項了。

pxNewListItem->pxNext->pxPrevious = pxNewListItem;

新插入列表項的指向上一個列表項的指針pxNewListItem->pxPrevious指向了輔助列表項pxIterator。很顯然要鏈接起來嘛!

pxNewListItem->pxPrevious = pxIterator;

同理,pxIterator列表項的指向下一個列表項的指針則指向新插入的列表項了pxNewListItem。

pxIterator->pxNext = pxNewListItem;

而其餘沒改變指向的地方不需改動。(圖中的兩條直線作的鏈接線是不須要改動的)
當插入完成的時候,記錄一下新插入的列表項屬於哪一個列表。而且讓該列表下的列表項數目加一。

pxNewListItem->pvContainer = ( void * ) pxList;
         ( pxList->uxNumberOfItems )++;

刪除列表項

源碼:

UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
/* The list item knows which list it is in.  Obtain the list from the list
item. */
List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;
    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
    pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
    /* Only used during decision coverage testing. */
    mtCOVERAGE_TEST_DELAY();
    /* Make sure the index is left pointing to a valid item. */
    if( pxList->pxIndex == pxItemToRemove )
    {
        pxList->pxIndex = pxItemToRemove->pxPrevious;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
    pxItemToRemove->pvContainer = NULL;
    ( pxList->uxNumberOfItems )--;
    return pxList->uxNumberOfItems;
}

其實刪除是很簡單的,不用想都知道,要刪除列表項,那確定要知道該列表項是屬於哪一個列表吧,pvContainer就是記錄列表項是屬於哪一個列表的。

刪除就是把列表中的列表項從列表中去掉,其本質其實就是把他們的鏈接關係刪除掉,而後讓刪除的列表項的先後兩個列表鏈接起來就好了,假如是隻有一個列表項,那麼刪除以後,列表就回到了初始化的狀態了。

pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;

這兩句代碼就實現了將刪除列表項的先後兩個列表項鍊接起來。

按照上面的講解能夠理解這兩句簡單的代碼啦。

假如刪除的列表項是當前索引的列表項,那麼在刪除以後,列表中的pxIndex就要指向刪除列表項的上一個列表項了。

if( pxList->pxIndex == pxItemToRemove )
{
      pxList->pxIndex = pxItemToRemove->pxPrevious;
}

固然還要把當前刪除的列表項的pvContainer指向NULL,讓它不屬於任何一個列表,由於,刪除的本質是刪除的僅僅是列表項的鏈接關係,其內存是沒有釋放掉的,假如是動態內存分配的話。
而且要把當前列表中列表項的數目返回一下。

至此,列表的源碼基本講解完畢。

最後

你們還能夠了解一下遍歷列表的宏,它在list.h文件中:

define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )                                        \
{                                                                                            \
List_t * const pxConstList = ( pxList );                                                    \
    /* Increment the index to the next item and return the item, ensuring */                \
    /* we don't return the marker used at the end of the list.  */                          \
    ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                            \
    if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )  \
    {                                                                                       \
        ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                        \
    }                                                                                       \
    ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;                                          \
}

這是一個宏,用於列表的遍歷,返回的是列表中列表項的pxOwner成員,每次調用這個宏(函數)的時候,其pxIndex索引會指向當前返回列表項的下一個列表項。

關注我

歡迎關注我公衆號

歡迎關注「物聯網IoT開發」公衆號

相關文章
相關標籤/搜索