FreeRTOS学习记录(三):列表、列表项(重要问题)

2022-04-23

依据:[野火]《FreeRTOS内核实现与应用开发实战指南》

链表由节点组成,节点与节点之间首尾相连。

链表的节点本身不能存储太多东西。

节点可以挂很多数据。

目录

单向链表

链表定义

操作链表

 双向链表

 链表与数组的对比

FreeRTOS 中链表

实现链表

链表节点数据结构

链表节点初始化

实现链表的根节点

定义链表根节点数据结构

链表根节点初始化

将节点插入到链表的尾部

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

将节点从列表删除

节点带参宏小函数

链表节点插入


可结合结构体文章分析FreeRTOS学习记录(三)(扩展)嵌套结构体展开_喜暖知寒的博客-CSDN博客

单向链表

链表定义

节点本身必须包含一个节点指针,用于指向后一个节点

节点都是一个自定义类型的数据结构,在这个数据结构里面可以有单个的数据、数组、指针数据和自定义的结构体数据类型等等信息

//定义结构体
struct node
{
    struct node *next; /* 指向链表的下一个节点 */
    char data1; /* 单个的数据 */
    unsigned char array[]; /* 数组 */
    unsigned long *prt /* 指针数据 */
    struct userstruct data2; /* 自定义结构体类型数据 */
    /* ...... */
}

通常的做法是节点里面只包含一个用于指向下一个 节点的指针。要通过链表存储的数据内嵌一个节点即可,这些要存储的数据通过这个内嵌 的节点即可挂接到链表中

伪代码::

/* 节点定义 */
struct node
{
    struct node *next; /* 指向链表的下一个节点 */
}

struct userstruct
{
    /* 在结构体中,内嵌一个节点指针,通过这个节点将数据挂接到链表 */
    struct node *next;
    /* 各种各样......,要存储的数据 */
}

  节点内嵌在数据结构中!!

操作链表

链表最大的作用是通过节点把离散的数据链接在一起,组成一个表,这大概就是链表的字面解释了吧。 链表常规的操作就是节点的插入和删除,为了顺利的插入,通常一条链表我们会人为地规定一个根节点,这个根节点称为生产者。通常根节点还会有一个节点 数器,用于统计整条链表的节点个数

 双向链表

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

 链表与数组的对比

链表是通过节点把离散的数据链接成一个表,通过对节点的插入和删除操作从而实现对数据的存取。而数组是通过开辟一段连续的内存来存储数据,这是数组和链表最大的区别。

数组的每个成员对应链表的节点,成员和节点的数据类型可以是标准的 C 类型或者是用户自定义的结构体。数组有起始地址和结束地址,而链表是一个圈,没有头和尾之分, 但是为了方便节点的插入和删除操作会人为的规定一个根节点。

FreeRTOS 中链表

FreeRTOS 中与链表相关的操作均在 list.hlist.c 这两个文件中实现

实现链表

链表节点数据结构

链表节点的数据结构在 list.h 中定义

struct xLIST_ITEM
{
    TickType_t xItemValue; /* 辅助值,用于帮助节点做顺序排列 */ (1)
    struct xLIST_ITEM * pxNext; /* 指向链表下一个节点 */ (2)
    struct xLIST_ITEM * pxPrevious; /* 指向链表前一个节点 */ (3)
    void * pvOwner; /* 指向拥有该节点的内核对象,通常是 TCB */(4)
    void * pvContainer; /* 指向该节点所在的链表 */ (5)
};

typedef struct xLIST_ITEM ListItem_t; /* 节点数据类型重定义 */ (6)

(1)TickType_t定义在portmacro.h,有16位、32位区别,默认32位

(4)用于指向该节点的拥有者,即该节点内嵌在哪个数据结构中,属于 哪个数据结构的一个成员。

(5)用于指向该节点所在的链表,通常指向链表的根节点。

链表节点初始化

链表节点初始化函数在 list.c 中实现


void vListInitialiseItem( ListItem_t * const pxItem )
{
    /* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */
    pxItem->pvContainer = NULL; 
    //是初始化的时候只需将pvContainer 初始化为空即可,表示该节点还没有插入到任何链表。
}

实现链表的根节点

定义链表根节点数据结构

链表根节点的数据结构在 list.h 中定义

typedef struct xLIST
{
    UBaseType_t uxNumberOfItems; /* 链表节点计数器 */ (1)
    ListItem_t * pxIndex; /* 链表节点索引指针 */ (2)
    MiniListItem_t xListEnd; /* 链表最后一个节点 */ (3)
} List_t

(1)链表节点计数器,用于表示该链表下有多少个节点,根节点除外。

(2)链表节点索引指针,用于遍历节点。

(3)链表最后一个节点。我们知道,链表是首尾相连的,是一个圈,首 就是尾,尾就是首,这里从字面上理解就是链表的最后一个节点,实际也就是链表的第一 个节点,我们称之为生产者。该生产者的数据类型是一个精简的节点,也在 list.h 中定义

//代码清单 链表精简节点结构体定义
struct xMINI_LIST_ITEM
{
    TickType_t xItemValue; /* 辅助值,用于帮助节点做升序排列 */
    struct xLIST_ITEM * pxNext; /* 指向链表下一个节点 */
    struct xLIST_ITEM * pxPrevious; /* 指向链表前一个节点 */
};

typedef struct xMINI_LIST_ITEM MiniListItem_t; /* 精简节点数据类型重定义 */

链表根节点初始化

链表节点初始化函数在 list.c 中实现

//链表根节点初始化
void vListInitialise( List_t * const pxList )
{
    /* 将链表索引指针指向最后一个节点 */(1)
    pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
    
    /* 将链表最后一个节点的辅助排序的值设置为最大,确保该节点就是链表的最后节点 */(2)
    pxList->xListEnd.xItemValue = portMAX_DELAY;
    
    /* 将最后一个节点的 pxNext 和 pxPrevious 指针均指向节点自身,表示链表为空 */(3)
    pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );
    pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );
 
    /* 初始化链表节点计数器的值为 0,表示链表为空 */(4)
    pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
}

根节点初始化:

(1)将链表索引指针指向最后一个节点,即第一个节点,或者第零个 节点更准确,因为这个节点不会算入节点计数器的值。

(2)将链表最后(也可以理解为第一)一个节点的辅助排序的值设置 为最大,确保该节点就是链表的最后节点(也可以理解为第一)。

(3)将最后一个节点(也可以理解为第一)的 pxNext 和 pxPrevious 指 针均指向节点自身,表示链表为空。

(4)初始化链表节点计数器的值为 0,表示链表为空

将节点插入到链表的尾部

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

//将节点插入到链表的尾部   //List_t根节点
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)
}

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

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

//将节点按照升序排列插入到链表
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
    ListItem_t *pxIterator;

    /* 获取节点的排序辅助值 */
    const TickType_t xValueOfInsertion = pxNewListItem->xItemValue; (1)

    /* 寻找节点要插入的位置 */ (2)
    if ( xValueOfInsertion == portMAX_DELAY )
    {
        pxIterator = pxList->xListEnd.pxPrevious;
    }
    else
    {
        for ( pxIterator = ( ListItem_t * ) &( pxList->xListEnd );
                pxIterator->pxNext->xItemValue <= xValueOfInsertion;
                pxIterator = pxIterator->pxNext )
        {
        /* 没有事情可做,不断迭代只为了找到节点要插入的位置 */
        }
    }
    /* 根据升序排列,将节点插入 */ (3)
    pxNewListItem->pxNext = pxIterator->pxNext; ①
    pxNewListItem->pxNext->pxPrevious = pxNewListItem; ②
    pxNewListItem->pxPrevious = pxIterator; ③
    pxIterator->pxNext = pxNewListItem; ④

    /* 记住该节点所在的链表 */
    pxNewListItem->pvContainer = ( void * ) pxList; ⑤

    /* 链表节点计数器++ */
    ( pxList->uxNumberOfItems )++; ⑥
}

(1)获取节点的排序辅助值。

(2)根据节点的排序辅助值,找到节点要插入的位置,按照升序排列。

(3)按照升序排列,将节点插入到链表。假设将一个节点排序辅助值 是 2 的节点插入到有两个节点的链表中,这两个现有的节点的排序辅助值分别是 1 和 3, 那么插入过程的示意图具体见图

将节点从列表删除

假设将一个有三个节点的链表中的中间 节点节点删除

//将节点从链表删除
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
    /* 获取节点所在的链表 */ 
    List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;
    /* 将指定的节点从链表删除*/ 
    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious; ①
    pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext; ②

    /*调整链表的节点索引指针 */ 
    if ( pxList->pxIndex == pxItemToRemove )
    {
        pxList->pxIndex = pxItemToRemove->pxPrevious;
    }

    /* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */
    pxItemToRemove->pvContainer = NULL; ③

    /* 链表节点计数器-- */
    ( pxList->uxNumberOfItems )--; ④

    /* 返回链表中剩余节点的个数 */
    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 ); 
    /* 节点索引指向链表第一个节点 */ 
    ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; 
    /* 这个操作有啥用? */ 
    if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) 
    { 
        ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; 
    } 
    /* 获取节点的 OWNER,即 TCB */ \
    ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \
}

链表节点插入

//链表节点插入实验

/*
*************************************************************************
* 包含的头文件
*************************************************************************
*/
#include "list.h"

/*
*************************************************************************
* 全局变量
*************************************************************************
*/

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

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



/*
************************************************************************
* main 函数
************************************************************************
*/
/*
int main(void)
{
    /* 链表根节点初始化 */
    vListInitialise( &List_Test ); (3)

    /* 节点 1 初始化 */
    vListInitialiseItem( &List_Item1 ); (4)
    List_Item1.xItemValue = 1;

    /* 节点 2 初始化 */
    vListInitialiseItem( &List_Item2 );
    List_Item2.xItemValue = 2;

    /* 节点 3 初始化 */
    vListInitialiseItem( &List_Item3 );
    List_Item3.xItemValue = 3;

    /* 将节点插入链表,按照升序排列 */ (5)
    vListInsert( &List_Test, &List_Item2 );
    vListInsert( &List_Test, &List_Item1 );
    vListInsert( &List_Test, &List_Item3 );
    for (;;)
    {
        /* 啥事不干 */
    }
}

(1)定义链表根节点,有根了,节点才能在此基础上生长。

(2)定义 3 个普通节点。

(3)链表根节点初始化,如图

 (4)节点初始化,其中 xItemValue 等于初始化值。如图

 (5)将节点按照他们的排序辅助值做升序排列插入到链表,如图

链表是实现任务切换重要的一环!!仔细学习!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值