一、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 链表根节点初始化

根节点初始化

void vListInitialise( List_t * const pxList )
{
    /* 将链表索引指针指向根节点; */

    pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );           
    
    /* 将链表根节点的辅助排序的值设置为最大,确保该节点就是链表的最后节点; */

    pxList->xListEnd.xItemValue = portMAX_DELAY;                        
    
    /* 将根节点的 pxNext 和 pxPrevious 指针均指向节点自身,表示链表为空; */

    pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );   
    pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );

    /* 初始化链表节点计数器的值为 0,表示链表为空; */

    pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
}
}

1.3.3 将节点插入到链表的尾部

FreeRTOS中数据结构链表思考的几个问题

FreeRTOS列表&列表项的源码解读

将节点插入到链表的尾部(可以理解为头部)就是将一个新的节点插入到一个空的链表。
将节点插入到链表的尾部

void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
    ListItem_t * const pxIndex = pxList->pxIndex;   /* 保存根节点的指向; */

    pxNewListItem->pxNext = pxIndex;                /* (1) 将新节点的下一个节点指向当前的根节点的指向;*/
    pxNewListItem->pxPrevious = pxIndex->pxPrevious;/* (2) 将新节点的上一个节点指向根节点的上一个节点; */
    
    pxIndex->pxPrevious->pxNext = pxNewListItem;    /* (3) 将根节点的上一个节点,此时还是根节点之前保存的状态,即指向根节点自己;
                                                       即将根节点自己的下一个节点指向新节点; */
    pxIndex->pxPrevious = pxNewListItem;            /* (4) 将根节点的上一个节点指向新节点; */
    
    pxNewListItem->pvContainer = ( void * ) pxList; /* (5) 记住该节点所在的链表; */
    ( pxList->uxNumberOfItems )++;                  /* (6) 链表节点计数器++; */
}

1.3.4 将节点按照升序排列插入到链表

将节点按照升序排列插入到链表,如果有两个节点的值相同,则新节点在旧节点的后面插入。

将节点按照升序的方式插入到链表中

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                         /* 如果条件不满足,则顺着链表向下遍历; */
            )
        {
            /* 没有事情可做,不断迭代只为了找到节点要插入的位置; */
        }
    }
    
    /* 根据升序排列,将节点插入 */

    pxNewListItem->pxNext = pxIterator->pxNext;         /* (1) */
    pxNewListItem->pxNext->pxPrevious = pxNewListItem;  /* (2) */
    pxNewListItem->pxPrevious = pxIterator;             /* (3) */
    pxIterator->pxNext = pxNewListItem;                 /* (4) */

    /* 记住该节点所在的链表; */

    pxNewListItem->pvContainer = ( void * ) pxList;     /* (5) */ 

    /* 链表节点计数器++; */

    ( pxList->uxNumberOfItems )++;
}

1.3.5 将节点从链表删除

链表中删除节点

UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
    /* 获取节点所在的链表; */

    List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;

    /* 将指定的节点从链表删除; */

    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious; /* (1) */
    pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;     /* (2) */

    /* 调整链表的节点索引指针; */
    
    if ( pxList->pxIndex == pxItemToRemove )    /* 如果当前删除的根节点; */
    {
        pxList->pxIndex = pxItemToRemove->pxPrevious;   /* 将上一个节点设置为根节点; */
    }

    /* 初始化该节点所在的链表为空,表示节点还没有插入任何链表; */

    pxItemToRemove->pvContainer = NULL;
    
    /* 链表节点计数器--; */
    
    ( pxList->uxNumberOfItems )--;

    /* 返回链表中剩余节点的个数; */

    return pxList->uxNumberOfItems;
}

1.3.6 节点宏函数


/* 初始化节点的拥有者; */

#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 );\

    /* 节点索引指向链表第一个节点; */

    ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;\
    
    if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )\
    {
        ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;
    }\

    /* 获取节点的 OWNER,即 TCB */

    ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;
}

1.3.7 案例

节点按照排序辅助值做升序排列插入到链表

#include "list.h"

/* 定义链表根节点; */
struct xLIST       List_Test;

/* 定义节点; */
struct xLIST_ITEM  List_Item1;
struct xLIST_ITEM  List_Item2;
struct xLIST_ITEM  List_Item3;

int main(void)
{	
	
    /* 链表根节点初始化; */
    vListInitialise( &List_Test );
    
    /* 节点 1 初始化; */
    vListInitialiseItem( &List_Item1 );
    List_Item1.xItemValue = 1;
    
    /* 节点 2 初始化; */    
    vListInitialiseItem( &List_Item2 );
    List_Item2.xItemValue = 2;
    
    /* 节点 3 初始化; */
    vListInitialiseItem( &List_Item3 );
    List_Item3.xItemValue = 3;
    
    /* 将节点插入链表,按照升序排列; */
    vListInsert( &List_Test, &List_Item2 );    
    vListInsert( &List_Test, &List_Item1 );
    vListInsert( &List_Test, &List_Item3 );    
    
    for(;;)
	{
		/* 挂起; */
	}
}

实验结果如下。
节点按照排序辅助值做升序排列插入到链表软件仿真数据

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

解琛

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

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

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

打赏作者

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

抵扣说明:

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

余额充值