FreeRTOS 的 list.c / list.h 文件中有 3 個數據結構、2 個初始化函數、2 個插入函數、1 個移除函數和一些巨集函數,鏈表是 FreeRTOS 中的重要數據結構 ...
FreeRTOS Kernel V10.3.1
FreeRTOS 的 list.c / list.h 文件中有 3 個數據結構、2 個初始化函數、2 個插入函數、1 個移除函數和一些巨集函數,鏈表是 FreeRTOS 中的重要數據結構,下述 “1、數據結構” 和 “2、操作鏈表” 兩個小節內容主要對其原理進行講解
1、數據結構
1.1、xLIST_ITEM
鏈表項,即節點,通常用鏈表項來表示一個任務
struct xLIST_ITEM
{
// 檢驗一個 鏈表項 數據是否完整
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE
// 排序值
configLIST_VOLATILE TickType_t xItemValue;
// 下一個 鏈表項
struct xLIST_ITEM * configLIST_VOLATILE pxNext;
// 前一個 鏈表項
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
// 記錄此 鏈表項 歸誰擁有,通常是 TCB (任務控制塊)
void * pvOwner;
// 擁有該 鏈表項 的 鏈表
struct xLIST * configLIST_VOLATILE pxContainer;
// 檢驗一個 鏈表項 數據是否完整
listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE
};
typedef struct xLIST_ITEM ListItem_t;
1.2、XMINI_LIST_ITEM
MINI 鏈表項,鏈表項的縮減版,專門用於表示鏈表尾節點,在 32 位 MCU 上不啟用鏈表項數據完整性校驗的情況下相比於普通的鏈表項節省了 8 個位元組(兩個指針)
struct xMINI_LIST_ITEM
{
// 檢驗一個 MINI鏈表項 數據是否完整
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;
1.3、xLIST
由多個鏈表項構成的鏈表,常用於區分不同任務狀態或任務優先順序,比如就緒狀態的任務放在就緒鏈表中,阻塞狀態的任務放在阻塞鏈表中,方便任務管理
typedef struct xLIST
{
// 檢驗一個 鏈表 數據是否完整
listFIRST_LIST_INTEGRITY_CHECK_VALUE
// 記錄 鏈表 中 鏈表項 數目
volatile UBaseType_t uxNumberOfItems;
// 遍歷 鏈表 的指針
ListItem_t * configLIST_VOLATILE pxIndex;
// 使用 MINI鏈表項 表示 鏈表尾部
MiniListItem_t xListEnd;
// 檢驗一個 鏈表 數據是否完整
listSECOND_LIST_INTEGRITY_CHECK_VALUE
} List_t;
2、操作鏈表
註意:由於不涉及數據校驗完整性,因此下述函數中關於校驗的所有部分將被刪除
2.1、初始化
2.1.1、vListInitialiseItem( )
初始化鏈表項函數
將鏈表項的 pxContainer 成員設置為 NULL ,因為初始化的時候該鏈表項未被任何鏈表包含
void vListInitialiseItem( ListItem_t * const pxItem )
{
// 確保鏈表項未被記錄在鏈表中
pxItem->pxContainer = NULL;
}
2.1.2、vListInitialise( )
初始化鏈表函數,具體步驟如下
- 將鏈表當前指針 pxIndex 指向尾鏈表項 xListEnd
- 確保尾鏈表項 xListEnd 被排在鏈表最尾部
- 將尾鏈表項 xListEnd 的前/後鏈表項指針均指向自己,因為初始化鏈表時只有尾鏈表項
- 鏈表中有 0 個鏈表項
void vListInitialise( List_t * const pxList )
{
// 鏈表當前指針指向 xListEnd
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
// 設置鏈表尾鏈表項排序值為最大, 保證 xListEnd 會被放在鏈表的尾部
pxList->xListEnd.xItemValue = portMAX_DELAY;
// 尾鏈表項 xListEnd 的前/後鏈表項指針均指向自己
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );
// 初始化時鏈表中有 0 個鏈表項
pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
}
2.2、插入
2.2.1、vListInsertEnd( )
將一個鏈表項插入到鏈表當前指針 pxIndex 指向的鏈表項前面,具體步驟如下
- 改變要插入鏈表項自身的 pxNext 和 pxPrevious
- 改變前一個鏈表項(也就是 pxIndex 指向的鏈表項的 Previous)的 pxNext
- 改變後一個鏈表項(也就是 pxIndex 指向的鏈表項)的 pxPrevious
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
// 獲取鏈表中當前指針 pxIndex 位置
ListItem_t * const pxIndex = pxList->pxIndex;
// 1. 改變自身 pxNext 和 pxPrevious
pxNewListItem->pxNext = pxIndex;
pxNewListItem->pxPrevious = pxIndex->pxPrevious;
// 2. 改變前一個鏈表項的 pxNext
pxIndex->pxPrevious->pxNext = pxNewListItem;
// 3. 改變後一個鏈表項的 pxPrevious
pxIndex->pxPrevious = pxNewListItem;
// 標記新插入的鏈表項所在的鏈表
pxNewListItem->pxContainer = pxList;
// 鏈表數量增加一
( pxList->uxNumberOfItems )++;
}
為方便繪圖演示,將鏈表的結構在圖上做了簡化,具體如下圖所示
註意:這裡 pxList->pxIndex 自初始化以來從未修改過,保持指向鏈表 xListEnd 鏈表項,下圖所有演示中,橙色虛線表示該步驟做了修改,黑色實線表示與上一步驟相比無修改
插入一個鏈表項
插入第二個鏈表項
插入第三個鏈表項
插入第四個鏈表項
2.2.2、vListInsert( )
將一個鏈表項按照鏈表中所有鏈表項的 xItemValue 值大小升序插入鏈表中,具體步驟如下
- 找到應該插入的位置(應該插入到 pxIterator 的後面)
- 改變要插入鏈表項自身 pxNext 和 pxPrevious
- 改變後一個鏈表項的 pxPrevious
- 改變前一個鏈表項的 pxNext (也即 pxIterator 指向的鏈表項的 pxNext)
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
// 記錄要插入的鏈表項的排序值
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
// 如果新插入的鏈表項排序值為最大值,直接插到尾節點 xListEnd 的前面
if( xValueOfInsertion == portMAX_DELAY )
{
pxIterator = pxList->xListEnd.pxPrevious;
}
else
{
/*
1. 遍歷鏈表,將當前鏈表項 pxIterator 與要插入的新的鏈表項 pxNewListItem
的 xItemValue 值比較,直到 pxIterator 的 xItemValue 大於 pxNewListItem
的 xItemValue 值,此時 pxNewListItem 應該插入到 pxIterator 的後面
*/
for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd );
pxIterator->pxNext->xItemValue <= xValueOfInsertion;
pxIterator = pxIterator->pxNext ){}
}
// 2. 改變要插入鏈表項自身 pxNext 和 pxPrevious
pxNewListItem->pxNext = pxIterator->pxNext;
pxNewListItem->pxPrevious = pxIterator;
// 3. 改變後一個鏈表項的 pxPrevious
pxNewListItem->pxNext->pxPrevious = pxNewListItem;
// 4. 改變前一個鏈表項的 pxNext
pxIterator->pxNext = pxNewListItem;
// 標記新插入的鏈表項所在的鏈表
pxNewListItem->pxContainer = pxList;
// 鏈表數量增加一
( pxList->uxNumberOfItems )++;
}
2.3、移除
2.3.1、uxListRemove( )
從鏈表中移除指定的鏈表項,具體步驟如下
- 改變後一個鏈表項的 pxPrevious
- 改變前一個鏈表項的 pxNext
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
List_t * const pxList = pxItemToRemove->pxContainer;
// 1. 改變後一個鏈表項 pxPrevious
pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
// 2. 改變前一個鏈表項 pxNext
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
// 確保索引指向有效的項目
if( pxList->pxIndex == pxItemToRemove )
{
pxList->pxIndex = pxItemToRemove->pxPrevious;
}
// 從鏈表中移除鏈表項後,該鏈表項不屬於任何鏈表
pxItemToRemove->pxContainer = NULL;
// 鏈表中鏈表項的數量減一
( pxList->uxNumberOfItems )--;
// 返回鏈表中鏈表項的數量
return pxList->uxNumberOfItems;
}
2.4、巨集函數
2.4.1、設置相關
// 設置 pxListItem 的 pxOwner 成員
#define listSET_LIST_ITEM_OWNER( pxListItem, pxOwner )
// 設置 pxListItem 的 xValue 成員值
#define listSET_LIST_ITEM_VALUE( pxListItem, xValue )
2.4.2、獲取相關
// 獲取 pxListItem 的 pxOwner 成員
#define listGET_LIST_ITEM_OWNER( pxListItem )
// 獲取 pxListItem 的 xValue 成員值
#define listGET_LIST_ITEM_VALUE( pxListItem )
// 獲取鏈表中頭鏈表項的 xItemValue 成員值
#define listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxList )
// 獲取鏈表中頭鏈表項地址
#define listGET_HEAD_ENTRY( pxList )
// 獲取某個鏈表項的下一個鏈表項地址
#define listGET_NEXT( pxListItem )
// 獲取鏈表中 xListEnd 的地址
#define listGET_END_MARKER( pxList )
// 獲取鏈表當前長度
#define listCURRENT_LIST_LENGTH( pxList )
// 將鏈表中 pxIndex 指向下一個鏈表項,用於獲取下一個鏈表項(任務)
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )
// 獲取鏈表中頭鏈表項的 pvOwner 成員
#define listGET_OWNER_OF_HEAD_ENTRY( pxList )
// 獲取鏈表項的 pxContainer 成員
#define listLIST_ITEM_CONTAINER( pxListItem )
2.4.3、判斷相關
// 判斷鏈表是否為空
#define listLIST_IS_EMPTY( pxList )
// 判斷鏈表項是否和鏈表匹配(鏈表項是否在該鏈表中)
#define listIS_CONTAINED_WITHIN( pxList, pxListItem )
// 判斷鏈表是否被初始化
#define listLIST_IS_INITIALISED( pxList )
3、鏈表移植
下麵直接將 FreeRTOS 內核中 list.c / list.h 文件移植到自己的工程中使用(當然,如果你已經懂得雙向鏈表數據結構的原理,你也可以構建屬於你自己的 list.c / list.h 文件)
移植可以總結為以下 4 個步驟
- 用 FreeRTOS Kernel V10.3.1 內核中 list.c / list.h 替換掉 RTOS 文件夾中的同名文件
- 在 portMacro.h 中統一一些用到基本類型
/* portMacro.h */
#include <stdint.h>
#define port_CHAR char
#define port_FLOAT float
#define port_DOUBLE double
#define port_LONG long
#define port_SHORT short
#define port_STACK_TYPE unsigned int
#define port_BASE_TYPE long
typedef port_STACK_TYPE StackType_t;
typedef long BaseType_t;
typedef unsigned long UBaseType_t;
typedef port_STACK_TYPE* StackType_p;
typedef long* BaseType_p;
typedef unsigned long* UBaseType_p;
#if(configUSE_16_BIT_TICKS == 1)
typedef uint16_t TickType_t;
#define portMAX_DELAY (TickType_t) 0xffff
#else
typedef uint32_t TickType_t;
#define portMAX_DELAY (TickType_t) 0xffffffffUL
#endif
#define pdFALSE ((BaseType_t) 0)
#define pdTRUE ((BaseType_t) 1)
#define pdPASS (pdTRUE)
#define pdFAIL (pdFALSE)
configUSE_16_BIT_TICKS
是一個巨集,用於 TickType_t
類型位數,具體定義如下
/* FreeRTOSConfig.h */
// 設置 TickType_t 類型位 16 位
#define configUSE_16_BIT_TICKS 0
- 刪除掉 list.c 文件中所有的 mtCOVERAGE_TEST_DELAY() 測試函數
- 刪除掉 list.h 文件中所有的 PRIVILEGED_FUNCTION 巨集
完成後編譯工程應該不會出現錯誤,這樣實現 RTOS 簡單內核關鍵的雙鏈表數據結構就完成了