一、FreeRTOS 链表

FreeRTOS

作者:解琛
时间:2020 年 8 月 18 日

一、链表

[野火®]《FreeRTOS 内核实现与应用开发实战—基于STM32》

链表就好比一个圆形的晾衣架,晾衣架上面有很多钩子,钩子首尾相连。链表也是,链表由节点组成,节点与节点之间首尾相连。晾衣架的钩子本身不能代表很多东西,但是钩子本身却可以挂很多东西。

同样,链表也类似,链表的节点本身不能存储太多东西,或者说链表的节点本来就不是用来存储大量数据的,但是节点跟晾衣架的钩子一样,可以挂很多数据。
链表示意图

1.1 单向链表

单向链表中共有 n 个节点,前一个节点都有一个箭头指向后一个节点,首尾相连,组成一个圈。
单向链表
节点本身必须包含一个节点指针,用于指向后一个节点。要通过链表存储的数据内嵌一个节点即可,这些要存储的数据通过这个内嵌的节点即可挂接到链表中,就好像晾衣架的钩子一样,把衣服挂接到晾衣架中。
节点内嵌在一个结构体中

链表作用是通过节点把离散的数据链接在一起,组成一个表。

链表常规的操作就是节点的插入和删除。

为了顺利的插入,通常一条链表会人为地规定一个根节点,这个根节点称为生产者。

通常根节点还会有一个节点计数器,用于统计整条链表的节点个数。
带根节点的链表

1.2 双向链表

双向链表与单向链表的区别就是节点中有两个节点指针,分别指向前后两个节点。
双向链表

1.3 FreeRTOS 链表实现

1.3.1 实现链表节点

1.3.1.1 定义链表结构结构

链表结构示意图

struct xLIST_ITEM
{
   
   
    TickType_t          xItemValue;     /* 辅助值,用于帮助节点做顺序排列; */ 
    struct xLIST_ITEM * pxNext;         /* 指向链表下一个节点; */            
    struct xLIST_ITEM * pxPrevious;     /* 指向链表前一个节点; */
    void *              pvOwner;        /* 用于指向该节点的拥有者,即该节点内嵌在哪个数据结构中,
                                           属于哪个数据结构的一个成员;*/
    void *              pvContainer;    /* 用于指向该节点所在的链表,通常指向链表的根节点;*/
};

typedef struct xLIST_ITEM ListItem_t;   /* 节点数据类型重定义; */
1.3.1.2 链表节点初始化
void vListInitialiseItem( ListItem_t * const pxItem )
{
   
   
    /* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */

    pxItem->pvContainer = NULL;
}

链表节点 ListItem_t 总共有 5 个成员,但是初始化的时候只需将 pvContainer 初始化为空即可,即清除其指向的链表,表示该节点还没有插入到任何链表。

1.3.2 实现链表根节点

1.3.2.1 定义链表根节点的数据结构

链表根节点

typedef struct xLIST
{
   
   
    UBaseType_t     uxNumberOfItems;    /* 链表节点计数器,用于表示该链表下有多少个节点,根节点除外; */
    ListItem_t *    pxIndex;            /* 链表节点索引指针,用于遍历节点; */
    MiniListItem_t  xListEnd;           /* 链表最后一个节点,实际也就是链表的第一个节点,即生产者; */
} List_t;

struct xMINI_LIST_ITEM
{
   
   
    TickType_t xItemValue;              /* 辅助值,用于帮助节点做升序排列; */
    struct xLIST_ITEM * pxNext;         /* 指向链表下一个节点; */
    struct xLIST_ITEM * pxPrevious;     /* 指向链表前一个节点; */
};

typedef struct xMINI_LIST_ITEM MiniListItem_t;  /* 精简节点数据类型重定义; */
1.3.2.2 链表根节点初始化<
### FreeRTOS 链表的使用与实现 #### 数据结构解析 FreeRTOS中的链表通过`struct xLIST_ITEM`来表示单个节点[^2]: ```c struct xLIST_ITEM { TickType_t xItemValue; struct xLIST_ITEM * pxNext; struct xLIST_ITEM * pxPrevious; void * pvOwner; void * pvContainer; }; typedef struct xLIST_ITEM ListItem_t; ``` - `xItemValue`: 用于辅助节点按特定顺序排列。 - `pxNext`, `pxPrevious`: 分别指向前驱和后继节点,形成双向链接关系。 - `pvOwner`: 指向拥有当前节点的任务控制块(TCB),即任务描述符。 - `pvContainer`: 表明该节点所属的具体列表实例。 对于简化版本的小型列表项(`MiniListItem_t`),则省去了`pvOwner`和`pvContainer`字段[^3]。 #### 列表定义 整个列表由`List_t`结构体表示: ```c typedef struct xLIST { volatile UBaseType_t uxNumberOfItems; ListItem_t * configLIST_VOLATILE pxIndex; MiniListItem_t xListEnd; } List_t; ``` 其中, - `uxNumberOfItems`记录了列表内的有效条目数; - `pxIndex`作为迭代器作用于遍历过程之中; - 特殊哨兵节点`xListEnd`被置于队列末端以方便检测边界条件。 #### 初始化操作 创建并初始化个新的空闲列表可通过如下方式完成: ```c void vListInitialise( List_t * const pxList ) { /* Initialize the list end */ memset((void *) &(pxList->xListEnd), 0, sizeof(pxList->xListEnd)); /* Set up a circular linked list with only one item (the dummy node). */ pxList->xListEnd.xItemValue = portMAX_DELAY; pxList->xListEnd.pxNext = &pxList->xListEnd; pxList->xListEnd.pxPrevious = &pxList->xListEnd; /* The number of items in an empty list is obviously zero. */ pxList->uxNumberOfItems = (UBaseType_t) 0; } ``` 这段代码片段展示了如何设置个仅含虚拟头结点的循环双端链表,并将其计数值设为零[^1]。 #### 插入新元素 当需要往已有的有序序列里加入新的成员时,可以采用下面的方法: ```c void vListInsert(List_t* pxList, ListItem_t* pxNew){ // Find where to place this new element within the sorted order. while ((pxIterator != NULL) && (listGET_LIST_ITEM_VALUE(pxIterator) <= pxNew->xItemValue)){ pxIterator = pxIterator->pxNext; } prvInsertBetweenNodes(pxPrevNodeOfTargetPosition, pxIterator, pxNew); ++(pxList->uxNumberOfItems); } static inline void prvInsertBetweenNodes( ListItem_t *const pxFirst, ListItem_t *const pxSecond, ListItem_t *const pxNewItemToBeInserted) { pxNewItemToBeInserted->pxNext = pxSecond; pxNewItemToBeInserted->pxPrevious = pxFirst; pxFirst->pxNext = pxNewItemToBeInserted; if (pxSecond != NULL) { pxSecond->pxPrevious = pxNewItemToBeInserted; } } ``` 上述逻辑首先定位到合适位置再执行实际插入动作,确保整体保持升序状态。同时更新前后相邻两者的连接指向以及调整总数统计。 #### 移除现有项目 要删除指定的目标,则需先找到目标所在的位置然后再解除其关联: ```c void vRemoveFromList(const ListItem_t * const pxItemToRemove){ /* Remove from its current position and update links around it.*/ pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext; if (NULL != pxItemToRemove->pxNext){ pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious; } --(pxItemToRemove->pvContainer->uxNumberOfItems); } ``` 这里不仅修正了周围邻居之间的相互引用还同步减少了容器内部的对象数目。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

解琛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值