1,前言
本文主要描述freertos中链表的实现方式。源码请参考list.c/list.h
2,基础概念
1,基础链表
如下所示,通常节点中包含了用户使用的信息。这种形式存在一定的局限性。当有其他功能也需要链表管理时,需要重新定义结构体,不具有通用性。
struct student
{
string name;
string id;
struct student *pnext;
};
struct school
{
string schoolname;
struct student head;
}
2,通用链表
本质上链表主要的任务就是串联同一种结构的数据信息块,这些数据信息块在内存中主要以非连续分配的形式存在。所以我们只要让链表管理好相关数据信息块的地址信息即可,
结点中不需要携带任何数据信息。如下图所示,结点被包含在数据信息块中。接下来要解决如何通过结点地址找到数据信息块。
方式1:
将结点定义在数据信息结构体首位,则结点地址等于数据信息地址。通过强制转换即可以得到数据信息块的首地址。
方式2:
利用结构体成员偏移量为固定值的特点,结点成员可以定义在数据信息结构体的任意位置,该方式主要在Linux系统中常用。
方式3:
在结点中加入结点拥有者的成员变量,用于存储对应数据信息块的地址。FreeRTOS采用了该方式。
3,FreeRTOS中的链表
1,链表结点数据结构体
struct xLIST_ITEM
{
configLIST_VOLATILE TickType_t xItemValue; /*< 结点值,用于优先级排序*/
struct xLIST_ITEM * configLIST_VOLATILE pxNext; /*< */
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /*< */
void * pvOwner; /*< 指向拥有该结点的所有者,通常为TCB*/
struct xLIST * configLIST_VOLATILE pxContainer; /*< 指向结点所属的列表 就绪队列 阻塞队列*/
};
typedef struct xLIST_ITEM ListItem_t;
2,链表数据结构体
typedef struct xLIST
{
volatile UBaseType_t uxNumberOfItems; /*< 该列表当前拥有的结点数量*/
ListItem_t * configLIST_VOLATILE pxIndex; /*< 用于遍历链表,指向结点*/
MiniListItem_t xListEnd; /*< 包含了一个最小内存空间的结点,用于表示链表的根结点*/
} List_t;
3,最小内存空间结点结构体
struct xMINI_LIST_ITEM
{
configLIST_VOLATILE TickType_t xItemValue;
struct xLIST_ITEM * configLIST_VOLATILE pxNext;
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
与正常链表结点相比,少占用8个字节,为链表的根节点,表征链表的边界。
4,链表以及结点操作
1,节点初始化
void vListInitialiseItem( ListItem_t * const pxItem )
{
/* 结点不属于任何链表即可 */
pxItem->pxContainer = NULL;
}
2,根结点初始化
void vListInitialise( List_t * const pxList )
{
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 ); /* 指向根结点 */
pxList->uxNumberOfItems = ( UBaseType_t ) 0U; /*结点数量为0*/
}
链表的next和prev指向根结点,则表示该链表为空。
3,结点尾插入
FreeRTOS的尾插入主要思想就是:在pxIndex指向的结点前插入。(下文中将pxIndex指向的结点简述为pxIndex结点)
即:
新结点的Next 应指向pxIndex结点,
新结点的Previous应指向pxIndex的前向结点,
pxIndex的前向结点的Next要指向新结点,
pxIndex结点的Previous指向新接点。
pxIndex结点的Next保持不变。
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t * const pxIndex = pxList->pxIndex;
pxNewListItem->pxNext = pxIndex; /*插入在index的前面*/
pxNewListItem->pxPrevious = pxIndex->pxPrevious; /*指向index的前向结点*/
pxIndex->pxPrevious->pxNext = pxNewListItem; /*前向结点的next指向新结点*/
pxIndex->pxPrevious = pxNewListItem; /*插入在index的前面*/
pxNewListItem->pxContainer = pxList;
( pxList->uxNumberOfItems )++;
}
如下提供部分场景下的操作示例。
1,链表为空时插入
2,链表非空时插入
如下示例中pxIndex指向Item2,所以新结点将会插入在item1与item2之间。
4,按序插入
Free RTOS中按序插入的核心思想是:插入在pxIterator指向结点的后面。(下文中pxIterator指向的结点简述为pxIterator结点)。
即:
新结点的Next应指向pxIterator的后向结点,
pxIterator的后向结点的Previous应指向新结点,
新结点的Previous应指向pxIterator结点,
pxIterator的Next应指向新结点,
pxIterator的Previous指向不变。
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t * pxIterator;
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;/*取出结点的排序值*/
if( xValueOfInsertion == portMAX_DELAY )
{
pxIterator = pxList->xListEnd.pxPrevious;/*根结点的前向结点就是当前整个链表的尾结点*/
}
else
{
for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext )
{ /* pxIterator指向头结点End时判断Item1与新插入结点的大小新加入结点不小于item1,
则pxIterator指向Item1,此时判断Item2的排序值与新结点发现新结点小于Item2,则新结点加入pxIterator后面的位置 */
}
}
pxNewListItem->pxNext = pxIterator->pxNext;
pxNewListItem->pxNext->pxPrevious = pxNewListItem;
pxNewListItem->pxPrevious = pxIterator;/*从这里可以看出新结点插入在pxIterator后面*/
pxIterator->pxNext = pxNewListItem;
pxNewListItem->pxContainer = pxList;
( pxList->uxNumberOfItems )++;
}
结合如下示意图分析for循环寻找的插入位置:
step1: pxIterator指向头节点End.
step2: 判断Item1的排序值是否小于新结点的排序值
step3: 若新结点的排序值大,则pxIterator往后向结点移动
step4: pxIterator指向Item1时,判断Item2与新结点的排序值。新结点小于Item2结点,则新结点加入pxIterator后面。
5,删除结点
FreeRTOS的删除结点的实现如下,无太多复杂逻辑。
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
/* 从被移除的结点中获取它所属的链表 */
List_t * const pxList = pxItemToRemove->pxContainer;/*先将后向结点的prev指向被移除节点的前向结点再将前向结点的next指向被移除结点的后向结点*/
pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
if( pxList->pxIndex == pxItemToRemove )
{
pxList->pxIndex = pxItemToRemove->pxPrevious;
}
else
{
}
pxItemToRemove->pxContainer = NULL;
( pxList->uxNumberOfItems )--;
return pxList->uxNumberOfItems;
}