01_FreeRTOS内核实现---列表与列表项

一、数据结构 — 双向循环链表

在FreeRTOS实现中用到了双向循环链表,这里对双向循环链表做简单整理,链表的详解可点击这里

1.定义

在这里插入图片描述
双向链表的每个节点中包含数据域和指针域,其中指针域包含两个指针,一个指针指向上一个节点,另一个指针指向下一个节点。图中的a0是头节点(非必须),其数据域没有实际意义,但是可以携带链表的长度等信息,其引入是为了操作的统一和方便而设立的,有了头节点,对在第一元素节点前插入节点和删除第一节点,其操作与其他节点就统一了。

2.插入

再两个节点间插入新的节点,需要改变插入位置处前后节点指针的指向以及新插入节点的指向,如图所示。
在这里插入图片描述

代码实现如下:

s->prior = p;
s->nnext = p->next;
p->next->prior = s;
p->next = s;

3.删除数据元素

在这里插入图片描述
代码实现如下:

p->prior->next = p->next;
p->next->prior = p->prior;

在实际应用,除上述代码之外,可能还需要释放内存,以免造成内存泄漏。

二、FreeRTOS中链表的实现

FreeRTOS中有关链表的实现均在list.h和list.c文件中。

1.实现链表节点

(1)定义链表节点数据结构

/* 定义链表节点的数据结构 */
struct xLIST_ITEM
{
    TickType_t xItemValue;              /* 辅助值,用于帮助节点进行顺序排列 */
    struct xLIST_ITEM* pxNext;          /* 指向链表的下一个节点 */
    struct xLIST_ITEM* pxPrevious;      /* 指向链表的上一个节点 */
    void* pvOwner;                      /* 指向拥有该节点的内核对象,通常是 TCB */
    void* pvContainer;                  /* 指向该节点所在的链表 */
};
typedef struct xLIST_ITEM ListItem_t;

TickType_t 是在portmacro.h中重定义的数据结构,若configUSE_BIT_TICKS为1,则TickType_t 为无符号的32位整形,否则为16位的无符号整形(configUSE_BIT_TICKS在FreeRTOSConfig.h中设置)。其他重定义的数据类型如下:

/* 数据类型重定义 */
#define portCHAR        char
#define portFLOAT       float
#define portDOUBLE      double
#define portLONG        long
#define portSHOrt       short
#define portSTACK_TYPE  uint32_t
#define portBASE_TYPE   long

typedef portSTACK_TYPE  StackType_t;
typedef long            BaseType_t;
typedef unsigned long   UBaseType_t;

#if(configUSE_BIT_TICKS == 1)
typedef uint16_t TickType_t;
#define portMAX_DELAY   (TickType_t)0xffff
#else
typedef uint32_t    TickType_t;
#define portMAX_DELAY   (TickType_t)0xffffffffUL
#endif

(2)链表节点初始化

/* 链表节点初始化 */
void vListInitialiseItem(ListItem_t* const pxItem)   /* pxItem是指针常量,指向的值不可修改 */
{
    /* 初始化该节点所在的链表为空,表示该节点还没有插入任何链表 */
    pxItem->pvOwner = NULL;
}

链表节点初始化很简单,仅仅将pvContainer初始化为空,表示该节点该没有插入任何链表。

2.实现链表根节点

根节点可以看作是一种特殊的节点,它是链表的“根”,链表创建的时候它就存在了。根节点存在的意义在之前已经提到,它的作用是为了操作的统一和方便,有了根节点,对在第一元素节点前插入节点和删除第一节点,其操作与其他节点就统一了,同时它还可以携带其他和链表相关的信息,如该根节点中就定义了UBaseType_类型的变量uxNumberOfItems用来保存链表中的节点数(不包括根节点本身)。

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


/* 定义链表根节点的数据结构 */
typedef struct xLIST
{
    UBaseType_t uxNumberOfItems;            /* 链表节点计数器 */
    ListItem_t* pxIndex;                    /* 链表节点索引指针 */
    MiniListItem_t  xLinstEnd;              /* 链表的最后一个节点 */
}List_t;

根节点是List_t,如下图所示:
在这里插入图片描述
“MiniListItem_t xLinstEnd;”被做当作是最后一个节点,但其实由于链表是循环的,最后一个也可以是看作是第0个。MiniListItem_t 相比于ListItem_t,少了pvOwner和pvContainer,MiniListItem_t 是特殊的一个节点,属于根节点的一部分。

3.链表根节点初始化

创建一个根节点就等于创建了一个链表了,这样就可以在这个链表中插入节点,在链表创建以后需要对其进行初始化,根节点中总共包含5个变量,初始化也就对这个五个变量进行赋值,代码如下:

/* 链表根节点初始化 */
void vListInitialise(List_t* const pxList)
{
    /* 将链表索引指针指向最后一个节点 */
    pxList->pxIndex = (ListItem_t*)&(pxList->xLinstEnd);
    
    /* 将链表最后一个节点的辅助排序的值设置为最大,确保该节点就是链表的最后节点 */
    pxList->xLinstEnd.xItemValue = portMAX_DELAY;

    /* 将最后一个节点的pxNext和pxPrevious指针均指向自身,确保该节点就是链表的最后节点 */
    pxList->xLinstEnd.pxNext = (ListItem_t*)&(pxList->xLinstEnd);
    pxList->xLinstEnd.pxPrevious = (ListItem_t*)&(pxList->xLinstEnd);

    /* 初始化链表节点计数器的值为0,表示链表为空 */
    pxList->uxNumberOfItems = (UBaseType_t)0U;
}

将xItemValue 值设置为最大,确保在升序排列中 xLinstEnd 节点是最后一个节点(FreeRTOS有根据xItemValue 的值进行升序排列的函数,而没有降序排列,因此将xItemValue 设置为最大,即portMAX_DELAY,就意味着该节点为最后一个节点)。其他几个变量的初始化很容易理解,注意根几点自身不做计数。

4.链表的尾插入、升序排列插入和删除

FreeRTOS中对链表的操作有尾部插入、升序排列插入和删除三种操作,其实现原理和一般的链表是一致的。
(1)将节点插入链表的尾部

/* 将节点插入链表的尾部 */
void vListInsertEnd(List_t* const pxList,ListItem_t* const pxNewListItem)
{
    ListItem_t* const pxIndex = pxList->pxIndex;

    pxNewListItem->pxNext = pxIndex;
    pxNewListItem->pxPrevious = pxIndex->pxPrevious;

    pxIndex->pxPrevious->pxNext = pxNewListItem;
    pxIndex->pxPrevious = pxNewListItem;

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

    (pxList->uxNumberOfItems) ++;
}

将新节点插入链表中,就要修改插入位置前一个节点的pxNext指针的指向、插入位置后一个节点的pxPrevious指针的指向以及新节点的两个指针的指向。在代码实现上pxNewListItem->pxNext = pxIndex;和 pxNewListItem->pxPrevious = pxIndex->pxPrevious;分别是定新节点的下一个节点和上一个节点,pxIndex->pxPrevious->pxNext = pxNewListItem;指定新节点前一个节点的下一个节点,pxIndex->pxPrevious = pxNewListItem;指定最后一个节点的前一个节点(由于是插入尾部,所以最后一个节点的前一个节点就是新节点),看起来很绕,但无非就是修改插入新节点后受影响的这四个指针。

(2)将节点按照升序排列插入链表
注意,按照FreeRTOS的代码实现,如果两个节点的值相同,则新节点在旧节点后面插入。

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

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

    /* 寻找节点要插入的位置 */
    if(xValueOfInserttion == portMAX_DELAY)
    {
        pxIterator = pxList->xLinstEnd.pxPrevious;
    }
    else
    {
        for(pxIterator = (ListItem_t*)&(pxList->xLinstEnd);
        pxIterator->pxNext->xItemValue <= xValueOfInserttion;
        pxIterator = pxIterator->pxNext)
        {

        }
    }
    /* 根据升序排列,将节点插入 */
    pxNewListItem->pxNext = pxIterator->pxNext;
    pxNewListItem->pxPrevious = pxIterator;
    
    pxNewListItem->pxNext->pxPrevious = pxNewListItem;
    pxIterator->pxNext = pxNewListItem;


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

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

在代码实现上可以分为两大步,第一步是找到插入位置,第二步插入新节点。在寻找插入位置时,需要先定义一个用于遍历的指针ListItem_t* pxIterator,然后将其指向最后一个节点(也可以看作是第0个节点),即pxIterator = (ListItem_t*)&(pxList->xLinstEnd),然后从第一个节点开始比较,pxIterator->pxNext->xItemValue <= xValueOfInserttion,直到找到插入位置,没比较完一次之后需要将遍历指针指向下一个节点:pxIterator = pxIterator->pxNext。在插入时用pxNewListItem->pxNext = pxIterator->pxNext和 pxNewListItem->pxPrevious = pxIterator分别指定新节点的下一个节点和上一个节点,pxIterator->pxNext = pxNewListItem指定新节点前一个节点的下一个节点,pxNewListItem->pxNext->pxPrevious = pxNewListItem指定新节点后一个节点的前一个节点。

(3)将节点从链表中删除

/* 将节点从链表中删除 */
UBaseType_t uxListRemove(ListItem_t* const pxItemRemove)
{
    /* 获取节点所在链表 */
    List_t* const pxList = (List_t*)pxItemRemove->pvContainer;
    /* 将指定的节点从链表删除 */
    pxItemRemove->pxNext->pxPrevious = pxItemRemove->pxPrevious;
    pxItemRemove->pxPrevious->pxNext = pxItemRemove->pxNext;
    /* 调整链表的节点索引指针 */
    if(pxList->pxIndex == pxItemRemove)
    {
        pxList->pxIndex = pxItemRemove->pxPrevious;
    }
    /* 初始化该节点所在的链表为空 */
    pxItemRemove->pvContainer = NULL;
    /* 链表计数器减1  */
    (pxList->uxNumberOfItems)--;
    /* 返回链表中剩余的节点数 */
    return pxList->uxNumberOfItems;
}

由于每个节点中会有一个指针其所在的链表,因此只需要将要删除的节点传入函数而不需要传入其所在的链表。删除节点相对较简单,重点是调整删除位置处前后节点的指针指向,用pxItemRemove->pxNext->pxPrevious = pxItemRemove->pxPrevious将删除节点的下一个节点的前一个节点调整为删除节点的前一个节点,用pxItemRemove->pxPrevious->pxNext = pxItemRemove->pxNext将删除节点的前一个节点的下一个节点设置为删除节点的下一个节点,听起来很绕,想清楚之后就很简单了,这就是指针的魅力!!!

5.节点带参宏函数

在list.h中还定义了各种各样的带参宏,以便对节点进行一些简单的操作。

/*********************  节点带参宏函数 ********************/

/* 初始化节点的拥有者 */
#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)

/* 获取链表的入口节点 */
#define listGET_HEAD_ENTRY(pxList)      (((pxList)->xListEnd).pxNext)

/* 获取节点的下一个节点 */
#define listGET_NEXT(pxListItem)        ((pxListItem)->pxNext)

/* 获取链表的最后一个节点 */
#define lsitGET_END_MARKER(pxList)      ((ListItem_t const *)&(((pxList)->xListEnd)))

/* 判断链表是否为空 */
#define listLIST_IS_ENPTY(pxList)   ((BaseType_t)(((pxList)->uxNumberOfItems) == (UBaseType_t)0))

/* 获取链表的节点数 */
#define listCURRENT_LIST_LENGTH(pxList)     ((pxList)->uxNumberOfItems)

/* 获取链表第一个节点的OWNER */
#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;\
	pxTCB = pxConstList-?pxIndex->pvOwner;\
}

三、验证

1.实验要求

至此,FreeRTOS的链表相关代码已经全部实现,下面通过一个简单的实验验证上述代码。实验很简单,创建一个链表和三个节点,然后:
(1)将这三个节点按照升序排列插入链表中,观察各个指针的指向
(2)将中间节点删除,观察各个指针的指向及变化
(3)将上一步删除的指针插入到链表尾部,观察各个指针的指向及变化

2.实验步骤

(1)在mian.c文件中定义一个链表和三个节点,代码如下:

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

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

(2)分别对链表和节点进行初始化

/* 链表根节点初始化 */
	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;

(3)将节点插入链表

/* 将节点插入链表 */
	vListInsert(&List_Test,&List_Item1);
	vListInsert(&List_Test,&List_Item2);
	vListInsert(&List_Test,&List_Item3);

(4)观察各个指针的指向
在这里插入图片描述
可以看出各个节点的指针指向没有问题,形成一个环。
(5)删除第二个节点,观察指针指向及其变化
在这里插入图片描述
(6)将第二个节点插入到链表尾部,观察指针指向及其变化
在这里插入图片描述
从指针的指向上可以看出,节点2确实插入了链表尾。至此,FreeRTOS的列表和列表项就实现了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
《161204_mastering_the_freertos_real_time_kernel-a_hands-on_tutorial_guide》是一本关于FreeRTOS实时操作系统的教程指南。该书的目标是帮助读者深入了解FreeRTOS内核的使用和原理,并通过实践目来提高他们的技能。 这本指南首先介绍了FreeRTOS的基本概念,包括任务、调度器、队列、信号量和定时器等。接着,它详细解释了如何在不同的硬件平台中运行FreeRTOS,并提供了实践的示例和代码。读者可以学习如何创建任务,配置调度器,并在任务之间实现通信和同步。 《161204_mastering_the_freertos_real_time_kernel-a_hands-on_tutorial_guide》不仅仅停留在理论层面,还提供了一系列实践目,让读者通过实际操作来加深对FreeRTOS的理解。这些目涵盖了各个方面,例如任务管理、队列和信号量的使用、中断处理等。 此外,该指南还介绍了FreeRTOS的高级功能,如软件定时器、任务通知和事件组等。通过学习这些功能,读者可以更好地利用FreeRTOS来完成多线程的实时应用程序。 总的来说,《161204_mastering_the_freertos_real_time_kernel-a_hands-on_tutorial_guide》是一本全面而实用的FreeRTOS教程指南。它适用于对FreeRTOS感兴趣的嵌入式系统开发者,无论是初学者还是有一定经验的使用者,都可以从中获得知识和技能的提升。通过理论与实践相结合,读者可以更好地掌握FreeRTOS内核,并应用于实际目中。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

忆昔z

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

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

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

打赏作者

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

抵扣说明:

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

余额充值