写在前面
本文主要是对于 FreeRTOS 中链表相关内容的详细解释,代码大部分参考了野火FreeRTOS教程配套源码,作了一小部分修改。
一、结构体定义
主要包含三种结构体:
- 普通节点结构体
- 结尾节点(mini节点)结构体
- 链表结构体
1.普通节点结构体
一个完整的节点,包含五个参数:
- 节点的序号,用于确定节点在链表中的位置
- 前指针,指向本节点的前一个节点
- 后指针,指向本节点的下一个节点
- 本节点所属的内核对象,通常是TCB(Task Control Block)是FreeRTOS中用于管理任务的数据结构。
- 该节点所属的链表
- 结构体定义如下:
/* 节点结构体定义 */
struct xLIST_ITEM
{
TickType_t xItemValue; /* 辅助值,用于帮助节点做顺序排列 */
struct xLIST_ITEM * pxNext; /* 指向链表下一个节点 */
struct xLIST_ITEM * pxPrevious; /* 指向链表前一个节点 */
void * pvOwner; /* 指向拥有该节点的内核对象,通常是TCB */
void * pvContainer; /* 指向该节点所在的链表 */
};
typedef struct xLIST_ITEM ListItem_t; /* 节点数据类型重定义 */
2.结尾节点(mini节点)结构体
Mini节点没有所属内核和所属链表信息,主要被用作双向列表的结尾,只有三种信息:
- 节点序号值,标识节点在链表中的位置
- 前指针
- 后指针
- 结构体定义如下:
/* mini节点结构体定义,作为双向链表的结尾
因为双向链表是首尾相连的,头即是尾,尾即是头 */
struct xMINI_LIST_ITEM
{
TickType_t xItemValue; /* 辅助值,用于帮助节点做升序排列 */
struct xLIST_ITEM * pxNext; /* 指向链表下一个节点 */
struct xLIST_ITEM * pxPrevious; /* 指向链表前一个节点 */
};
typedef struct xMINI_LIST_ITEM MiniListItem_t; /* 最小节点数据类型重定义 */
3.链表结构体
链表需要三种参数:
- 链表节点计数器,统计链表中的节点总数
- 链表节点索引指针,指向链表中目前要操作的节点
- 链表最后一个节点的指针
- 结构体定义如下:
/* 链表结构体定义 */
typedef struct xLIST
{
UBaseType_t uxNumberOfItems; /* 链表节点计数器 */
ListItem_t * pxIndex; /* 链表节点索引指针 */
MiniListItem_t xListEnd; /* 链表最后一个节点 */
} List_t;
二、链表基本函数定义
主要包括下面几种函数:
- 节点初始化函数
- 空链表初始化函数
- 将新节点插入到链表尾部的函数
- 将新节点按升序插入链表的函数
- 从链表中删除节点的函数
1.节点初始化函数
传入要初始化的节点,将该节点的所属链表参数设置为空即可。
- 函数定义如下:
//节点初始化
void vListInitialiseItem(ListItem_t * const pxItem)
{
pxItem->pvContainer = NULL; //将该节点所在链表初始化为空,表示该节点还没有插入任何链表
}
2. 空链表初始化函数
需要的步骤如下:
- 将指针索引指向最后一个节点
- 最后一个节点的排序值设置为最大(取决于portmacro中portMAX_DELAY宏定义)
- 最后一个节点的前后指针都指向节点自身
- 链表节点计数器设置为 0
- 代码如下:
//空链表初始化
void vListInitialise(List_t * const pxList)
{
//指针索引指向最后一个节点,pxList->xListEnd是一个值,需要取地址后强制类型转换
pxList->pxIndex = (ListItem_t *)&pxList->xListEnd;
//最后一个节点的辅助值设置为最大
pxList->xListEnd.xItemValue = portMAX_DELAY;
//最后一个节点的前指针和后指针都指向节点自身,表示链表为空
pxList->xListEnd.pxNext = (ListItem_t *)&pxList->xListEnd;
pxList->xListEnd.pxPrevious = (ListItem_t *)&pxList->xListEnd;
//初始化链表节点计数器为0,表示链表为空
pxList->uxNumberOfItems = (UBaseType_t)0U;
}
3. 将新节点插入到链表尾部的函数
- 需要的步骤如下:
- 拿出指向链表最后一个节点的指针
- 将新的节点插入到原来最后节点的前面
- 设置新插入节点所属的链表
- 链表节点计数器++
- 代码如下:
//将节点插入到链表的尾部
void vListInsertEnd(List_t * const pxList, ListItem_t * const pxNewListItem)
{
//拿出指向链表最后节点的指针
ListItem_t * const pxIndex = pxList->pxIndex;
//将新的节点插入到原来最后节点前面
pxNewListItem->pxNext = pxIndex;
pxNewListItem->pxPrevious = pxIndex->pxPrevious;
pxIndex->pxPrevious->pxNext = pxNewListItem;
pxIndex->pxPrevious = pxNewListItem;
//设置插入的新节点所在的链表
pxNewListItem->pvContainer = (void *)pxList;
//链表节点计数器+1
(pxList->uxNumberOfItems)++;
}
4. 将新节点按升序插入链表的函数
- 需要的步骤如下:
- 定义迭代器(节点指针)用于查找插入的位置
- 获取新插入节点的序号
- 寻找节点插入的位置
- 进行节点插入操作
- 修改新插入的节点所在的链表
- 链表节点计数器++
- 代码如下:
//将节点按照升序排列插入链表
void vListInsert(List_t * pxList, ListItem_t * const pxNewListItem)
{
//1. pxIterator 用于查找插入位置
ListItem_t * pxIterator;
//2. 获取新插入节点的排序辅助值
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
//3. 寻找节点要插入的位置
if(xValueOfInsertion == portMAX_DELAY){ //如果是最大的节点,插入在链表的最后即可
pxIterator = pxList->xListEnd.pxPrevious;
}
else{
//for循环寻找插入位置
//pxIterator = (ListItem_t *)&pxList->xListEnd 环状链表,尾就是头
for(pxIterator = (ListItem_t *)&pxList->xListEnd; pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext);
}
//4. 插入新节点
pxNewListItem->pxNext = pxIterator->pxNext;
pxNewListItem->pxNext->pxPrevious = pxNewListItem;
pxNewListItem->pxPrevious = pxIterator;
pxIterator->pxNext = pxNewListItem;
//5. 修改插入的新节点所在的链表
pxNewListItem->pvContainer = (void *)pxList;
//6. 链表节点计数器+1
(pxList->uxNumberOfItems)++;
}
5. 从链表中删除节点的函数
- 需要的步骤如下:
- 获取要删除的节点所在的链表
- 进行删除操作
- 如果链表索引指向要删除的节点,需更新索引使其指向要删除节点的前一个节点,确保索引指向有效
- 设置要删除节点的所属链表为空
- 链表节点计数器–
- 返回删除操作后链表中剩余的节点的数量
- 代码如下:
//从链表中删除节点
UBaseType_t uxListRemove(ListItem_t * const pxItemToRemove)
{
//1. 获取要删除的节点所在的列表
List_t * const pxList = (List_t *)pxItemToRemove->pvContainer;
//2. 将节点从链表中删除
pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
//3. 如果要删除的节点是目前链表的索引,就得把索引更新为要删除节点的前一个,确保索引指向有效的节点
if(pxList->pxIndex == pxItemToRemove){
pxList->pxIndex = pxItemToRemove->pxPrevious;
}
//4. 将要删除的节点所属链表设置为空,表示不属于任何链表
pxItemToRemove->pvContainer = NULL;
//5. 更新链表节点计数器
(pxList->uxNumberOfItems)--;
//6. 返回链表中剩余的节点数量
return pxList->uxNumberOfItems;
}
三、宏定义
这些宏定义主要是为了方便链表操作:
/*
************************************************************************
* 宏定义
************************************************************************
*/
/* 初始化节点的拥有者 */
#define listSET_LIST_ITEM_OWNER( pxListItem, pxOwner ) ( ( pxListItem )->pvOwner = ( void * ) ( pxOwner ) )
/* 获取节点拥有者 */
#define listGET_LIST_ITEM_OWNER( pxListItem ) ( ( pxListItem )->pvOwner )
/* 初始化节点排序辅助值 */
#define listSET_LIST_ITEM_VALUE( pxListItem, xValue ) ( ( pxListItem )->xItemValue = ( xValue ) )
/* 获取节点排序辅助值 */
#define listGET_LIST_ITEM_VALUE( pxListItem ) ( ( pxListItem )->xItemValue )
/* 获取链表根节点的节点计数器的值 */
#define listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxList ) ( ( ( pxList )->xListEnd ).pxNext->xItemValue )
/* 获取链表的入口节点 */
#define listGET_HEAD_ENTRY( pxList ) ( ( ( pxList )->xListEnd ).pxNext )
/* 获取链表的第一个节点 */
#define listGET_NEXT( pxListItem ) ( ( pxListItem )->pxNext )
/* 获取链表的最后一个节点 */
#define listGET_END_MARKER( pxList ) ( ( ListItem_t const * ) ( &( ( pxList )->xListEnd ) ) )
/* 判断链表是否为空 */
#define listLIST_IS_EMPTY( pxList ) ( ( BaseType_t ) ( ( pxList )->uxNumberOfItems == ( UBaseType_t ) 0 ) )
/* 获取链表的节点数 */
#define listCURRENT_LIST_LENGTH( pxList ) ( ( pxList )->uxNumberOfItems )
/* 获取链表节点的OWNER,即TCB */
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \
{ \
List_t * const pxConstList = ( pxList ); \
/* 节点索引指向链表第一个节点调整节点索引指针,指向下一个节点,
如果当前链表有N个节点,当第N次调用该函数时,pxInedex则指向第N个节点 */\
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
/* 当前链表为空 */ \
if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \
{ \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
} \
/* 获取节点的OWNER,即TCB */ \
( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \
}
#define listGET_OWNER_OF_HEAD_ENTRY( pxList ) ( (&( ( pxList )->xListEnd ))->pxNext->pvOwner )
后记
如果您觉得本文写得不错,可以点个赞激励一下作者!
如果您发现本文的问题,欢迎在评论区或者私信共同探讨!
共勉!