在FreeRTOS中存在着大量的基礎數據結構列表和列表項的操作,要想讀懂FreeRTOS的源代碼,就必須弄懂列表和列表項的操作。
一、C語言鏈表簡介
鏈表就好比一個圓形的晾衣架,晾衣架上有很多鉤子,鉤子首尾相連。鏈表也是,鏈表由節點組成,節點與節點之間首尾相連。晾衣架的鉤子本身不能代表很多東西,但是鉤子本身可以掛很多東西。同樣,鏈表也類似,鏈表的節點本身不可能存儲太多東西,但是節點跟晾衣架的鉤子一樣,可以掛很多數據。
鏈表分爲單向鏈表和雙向鏈表。
1、單向鏈表
單向鏈表如下圖所示,該鏈表中有n個節點,前一個節點都有一個箭頭指向後一個節點,首尾相連,組成一個圈。
節點本身必須包含一個節點指針,用於指向後一個節點,除了節點指針之外,節點本身還可以攜帶一些私有信息。通常做法是節點裏面只包含一個用於指向下一個節點的指針,要通過鏈表存儲的數據內嵌一個節點即可,這些要存儲的數據通過這個內嵌的節點即可掛接到鏈表中。
2、雙向鏈表
雙向鏈表與單向鏈表的區別就是節點中有兩個節點指針,分別指向前後兩個節點,其他完全一樣。
3、鏈表與數組對比
鏈表 | 數組 |
---|---|
通過節點把離散的數據連接成一個表 ,通過對節點的插入和刪除實現對數據的存取 | 通過開闢一段連續的內存存儲數據 |
是一個圈,沒有首尾之分,但會規定一個根節點 | 有起始地址和結束地址 |
數組的每個成員對應鏈表中的節點。
二、FreeRTOS中鏈表的實現
FreeRTOS中與鏈表相關的操作均在list.h和list.c這兩個文件中實現,list.h 第一次使用需要在 include 文件夾下面新建然後添加到工程freertos/source 這個組文件, list.c 第一次使用需要在 freertos 文件夾下面新建然後添加到工程 freertos/source 這個組文件。
1、實現鏈表節點
1.1 定義鏈表節點數據結構
鏈表節點的數據結構在list.h中定義,如下圖:
(1)輔助值 用於幫助節點做順序排列。該輔助值的數據類型TickType_t, 在 FreeRTOS 中,凡是涉及到數據類型的地方,FreeRTOS 都會將標準的 C 數據類型用 typedef 重新取一個類型名,這些經過重定義的數據類型放在 portmacro.h(portmacro.h 第一次使用需要在 include 文件夾下面新建然後添加到工程 freertos/source 這個組文件)這個頭文件。TickType_t 具 體 表 示 16 位 還 是 32 位 , 由configUSE_16_BIT_TICKS 這個宏決定, 當該宏定義爲 1 時, TickType_t 爲 16 位,否則爲32 位。該宏在FreeRTOSConfig.h(FreeRTOSConfig.h 第一次使用需要在 include 文件夾下面新建然後添加到工程 freertos/source 這個組文件) 中默認定義爲 0。
(4)指向該節點的擁有者,即該節點內嵌在哪個數據結構中。
(5)指向該節點所在鏈表,通常指向鏈表的根節點
1.2 鏈表節點初始化
鏈表節點初始化在list.c中完成。
鏈表節點ListItem_t總共有五個成員,初始化時只需將pvContainer初始化爲空即可,表示該節點還沒有插入任何節點。
2 實現鏈表根節點
2.1 定義鏈表根節點數據結構
列表根節點的數據結構在list.h中實現。
typedef struct xLIST
{
UBaseType_t uxNumberOfItems; /* 鏈表節點計數器 (1)*/
ListItem_t * pxIndex; /* 鏈表節點索引指針 (2)*/
MiniListItem_t xListEnd; /* 鏈表最後一個節點 (3)*/
} List_t;
(1):用於記錄該鏈表下節點個數,除根節點外
(2):用於遍歷節點
(3):鏈表最後一個節點。鏈表首尾相連,即鏈表的最後一個節點也是鏈表的第一個節點,我們稱之爲生產者。該生產者的數據類型定義在list.h中,如下:
struct xMINI_LIST_ITEM
{
TickType_t xItemValue; /* 輔助值,用於幫助節點做升序排列 */
struct xLIST_ITEM * pxNext; /* 指向鏈表下一個節點 */
struct xLIST_ITEM * pxPrevious; /* 指向鏈表前一個節點 */
};
typedef struct xMINI_LIST_ITEM MiniListItem_t; /* 精簡節點數據類型重定義 */
2.2 鏈表根節點初始化
鏈表根節點初始化在list.c中實現。
void vListInitialise( List_t * const pxList )
{
/* 將鏈表索引指針指向最後一個節點 */(1)
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
/* 將鏈表最後一個節點的輔助排序的值設置爲最大,確保該節點就是鏈表的最後節點 */(2)
pxList->xListEnd.xItemValue = portMAX_DELAY;
/* 將最後一個節點的 pxNext 和 pxPrevious 指針均指向節點自身,表示鏈表爲空 */(3)
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );
/* 初始化鏈表節點計數器的值爲 0,表示鏈表爲空 */(4)
pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
}
2.3 將節點插入到鏈表的尾部
鏈表根節點初始化在list.c中實現。將節點插入到鏈表的尾部(也可理解爲頭部)就是講一個新節點插入到一個空鏈表中。
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t * const pxIndex = pxList->pxIndex;
pxNewListItem->pxNext = pxIndex; (1)
pxNewListItem->pxPrevious = pxIndex->pxPrevious; (2)
pxIndex->pxPrevious->pxNext = pxNewListItem; (3)
pxIndex->pxPrevious = pxNewListItem; (4)
/* 記住該節點所在的鏈表 */
pxNewListItem->pvContainer = ( void * ) pxList; (5)
/* 鏈表節點計數器++ */
( pxList->uxNumberOfItems )++; (6)
}
2.4 將節點按照升序排列插入到鏈表
將節點按照升序排列插入到鏈表,如果兩個節點的值相同,則新節點在舊節點後面插入。
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
/* 獲取節點的排序輔助值 */
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue; (1)
/* 尋找節點要插入的位置 */ (2)
if ( xValueOfInsertion == portMAX_DELAY )
{
pxIterator = pxList->xListEnd.pxPrevious;
}
else
{
for ( pxIterator = ( ListItem_t * ) &( pxList->xListEnd );
pxIterator->pxNext->xItemValue <= xValueOfInsertion;
pxIterator = pxIterator->pxNext )
{
/* 沒有事情可做,不斷迭代只爲了找到節點要插入的位置 */
}
}
/* 根據升序排列,將節點插入 */ (3)
pxNewListItem->pxNext = pxIterator->pxNext;
pxNewListItem->pxNext->pxPrevious = pxNewListItem;
pxNewListItem->pxPrevious = pxIterator;
pxIterator->pxNext = pxNewListItem;
/* 記住該節點所在的鏈表 */
pxNewListItem->pvContainer = ( void * ) pxList;
/* 鏈表節點計數器++ */
( pxList->uxNumberOfItems )++;
}
2.4 將節點從鏈表中刪除
假設將一個有三個節點的鏈表中的中間節點刪除。
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
/* 獲取節點所在的鏈表 */
List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;
/* 將指定的節點從鏈表刪除*/
pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious; ①
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
/*調整鏈表的節點索引指針 */
if ( pxList->pxIndex == pxItemToRemove )
{
pxList->pxIndex = pxItemToRemove->pxPrevious;
}
/* 初始化該節點所在的鏈表爲空,表示節點還沒有插入任何鏈表 */
pxItemToRemove->pvContainer = NULL;
/* 鏈表節點計數器-- */
( pxList->uxNumberOfItems )--;
/* 返回鏈表中剩餘節點的個數 */
return pxList->uxNumberOfItems;
}
三、鏈表節點插入試驗
我們新建了一個根節點和三個普通節點,然後將這三個普通節點按照節點的排序輔助值做升序排列插入到鏈表中。
/*
*************************************************************************
* 包含的頭文件
*************************************************************************
*/
#include "list.h"
/*
*************************************************************************
* 全局變量
*************************************************************************
*/
/* 定義鏈表根節點 */
struct xLIST List_Test; (1)
/* 定義3個普通節點 */
struct xLIST_ITEM List_Item1; (2)
struct xLIST_ITEM List_Item2;
struct xLIST_ITEM List_Item3;
/*
************************************************************************
* main 函數
************************************************************************
int main(void)
{
/* 鏈表根節點初始化 */
vListInitialise( &List_Test ); (3)
/* 節點 1 初始化 */
vListInitialiseItem( &List_Item1 ); (4)
List_Item1.xItemValue = 1;
/* 節點 2 初始化 */
vListInitialiseItem( &List_Item2 );
List_Item2.xItemValue = 2;
/* 節點 3 初始化 */
vListInitialiseItem( &List_Item3 );
List_Item3.xItemValue = 3;
/* 將節點插入鏈表,按照升序排列 */ (5)
vListInsert( &List_Test, &List_Item2 );
vListInsert( &List_Test, &List_Item1 );
vListInsert( &List_Test, &List_Item3 );
while(1)
{
}
}
程序編譯之後,點擊調試按鈕,全速運行,然後把List_Test、List_Item1、List_Item2、List_Item3這四個全局變量添加到watch窗口進行觀察。
參考:[野火®]《FreeRTOS 內核實現與應用開發實戰—基於STM32》