FreeRTOS源码解析——列表

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值