第一次看列表與列表項的時候,感受很像是鏈表,雖然我本身的鏈表也不太會,可是就是感受很像。數據結構
在FreeRTOS中,列表與列表項使用得很是多,是FreeRTOS的一個數據結構,學習過數據結構的同窗都知道,數據結構能使咱們處理數據更加方便快速,能快速找到數據,在FreeRTOS中,這種列表與列表項更是必不可少的,能讓咱們的系統跑起來更加流暢迅速。函數
言歸正傳,FreeRTOS中使用了大量的列表(List)與列表項(Listitem),在FreeRTOS調度器中,就是用到這些來跟着任務,瞭解任務的狀態,處於掛起、阻塞態、仍是就緒態亦或者是運行態。這些信息都會在各自任務的列表中獲得。學習
看任務控制塊(tskTaskControlBlock)中的兩個列表項:spa
ListItem_t xStateListItem; /* <任務的狀態列表項目引用的列表表示該任務的狀態(就緒,已阻止,暫停)。*/ ListItem_t xEventListItem; /* <用於從事件列表中引用任務。*/
一個是狀態的列表項,一個是事件列表項。他們在建立任務就會被初始化,列表項的初始化是根據實際須要來初始化的,下面會說。操作系統
既然知道列表與列表項的重要性,那麼咱們來解讀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 )++; }
傳入的參數:
從末尾插入,那就要先知道哪裏是頭咯,咱們在列表中的成員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中的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開發」公衆號