深入理解FreeRTOS_学习笔记(10.链表)

**

文章大部分内容摘抄至B站的韦东山老师的深入了解FreeRTOS操作系统教程,若有不理解的地方,可点击链接学习韦老师的视频。

**

**

我们这一章学习一下freeRTOS的链表操作

**

文章目录

    • **
  • 前言
  • 一、链表是什么
  • 二、链表项
  • 三、插入链表尾部
  • 四、按序插入链表
  • 五、删除链表项
  • 六、带参数的宏定义(链表操作)
  • 总结


前言

链表是FreeRTOS中使用次数相当多的,我们这一章主要就是链表项定义,插入,删除等一些操作,并且通过源码分析。


一、链表是什么

在FreeRTOS中,链表是一种常见的数据结构,用于维护任务间的一些关系。链表是由多个节点链接起来的数据结构,每个节点包含了一个数据元素和一个指向下一个节点的指针。
在FreeRTOS中,链表通常用于维护就绪队列、挂起队列、延时队列等。每个任务都有一个TCB(任务控制块),TCB中维护了该任务的状态、优先级等信息。当一个任务进入就绪态时,会被插入到对应的就绪队列中,当一个任务在等待某个事件时,会被加入到对应的挂起队列中。这些队列都可以使用链表进行维护。
FreeRTOS提供了一些API,用于对链表进行操作,例如vListInitialise、vListInsert、uxListRemove等。开发人员可以通过这些API对链表进行插入、删除、搜索等操作。
使用链表可以方便地实现任务调度和事件处理,但是需要注意线程安全和正确性问题。特别是在多任务系统中,需要避免竞争条件和死锁情况。

二、链表项

任务会有句柄,链表会有链表项,在freertos中其实采用的是双向链表的数据结构。

typedef struct xLIST
{
    listFIRST_LIST_INTEGRITY_CHECK_VALUE /*如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值。*/
    volatile UBaseType_t uxNumberOfItems;//这个链表中有多少个链表项
    ListItem_t * configLIST_VOLATILE pxIndex;/*用于浏览列表。指向调用listGET_OWNER_OF_NEXT_ENTRY()返回的最后一个项目,也就是坐标*/
    MiniListItem_t xListEnd;/*链表的头结点,只起到站岗的作用,在双向循环链表中既可以说的头也可以说是尾结点,而且数据类型是一个精简的结点*/
    listSECOND_LIST_INTEGRITY_CHECK_VALUE /*如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值。*/
} List_t;

重点查看中间三个变量他们的类型,ListItem_t 和 MiniListItem_t 我们依次介绍一下
先是ListItem_t 结构体,这是链表中链表项的构造

struct xLIST_ITEM
{
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /*如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值。*/
    configLIST_VOLATILE TickType_t xItemValue;/*链表项的值。在大多数情况下,这用于按降序对列表进行排序 */
    struct xLIST_ITEM * configLIST_VOLATILE pxNext;/*指向链表中下一个ListItem_t*/
    struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;/*指向链表中上一个ListItem_t*/
    void * pvOwner;/*指向包含列表项的对象(通常是TCB)的指针。因此,在包含列表项的对象和列表项本身之间存在双向链接。*/
    struct xLIST * configLIST_VOLATILE pxContainer;/*指向放置此列表项的列表的指针(如果有)。*/
    listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE /*如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值。 */
};
typedef struct xLIST_ITEM ListItem_t;        

还有MiniListItem_t这个类型,这个是迷你版的,也就是少了很多无关变量,用于减少内存。


struct xMINI_LIST_ITEM
{
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE 
    configLIST_VOLATILE TickType_t xItemValue;
    struct xLIST_ITEM * configLIST_VOLATILE pxNext;
    struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

可以看到这个迷你的链表类型只有几个参数,重要的是指向前继,指向后续和项目值。
整个链表可以用以下这个图表示:
深入理解FreeRTOS_学习笔记(10.链表)_第1张图片

三、插入链表尾部

怎么将链表点插入链表尾部之中呢,如下图所示:
深入理解FreeRTOS_学习笔记(10.链表)_第2张图片
可以看出其实插入链表的尾部的话,并不是直接插入,而是插入pxIndex->pxprevious,也就是pxIndex的前继,保证了freertos的公平性。
我们可以查看插入尾部的源码:

void vListInsertEnd( List_t * const pxList,
                     ListItem_t * const pxNewListItem )
{
    ListItem_t * const pxIndex = pxList->pxIndex;//获取当前的位置
    listTEST_LIST_INTEGRITY( pxList );
    listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
    pxNewListItem->pxNext = pxIndex;//将新的链表项下一项指向pxIndex,也就是插入了pxIndex前面
    pxNewListItem->pxPrevious = pxIndex->pxPrevious;
    mtCOVERAGE_TEST_DELAY();

    pxIndex->pxPrevious->pxNext = pxNewListItem;//修改前后继
    pxIndex->pxPrevious = pxNewListItem;
    pxNewListItem->pxContainer = pxList;//记录是哪个链表

    ( pxList->uxNumberOfItems )++;//添加了一个链表项,链表项总数加1
}

四、按序插入链表

按序插入链表的源码:

void vListInsert( List_t * const pxList,
                  ListItem_t * const pxNewListItem )
{
    ListItem_t * pxIterator;
    const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
    listTEST_LIST_INTEGRITY( pxList );
    listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
	//判断链表中的值是否是最大值,确定是否是头结点,头结点的前一项也就是最后一项
    if( xValueOfInsertion == portMAX_DELAY )
    {
        pxIterator = pxList->xListEnd.pxPrevious;
    }
    else
    {

		//根据xValueOfInsertion来确定插入位置,这个步骤是在遍历这个链表,每次对比一次都自动移动到下一项。
        for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) 
        {
            
        }
    }
	//修改插入链表项的前后继
    pxNewListItem->pxNext = pxIterator->pxNext;
    pxNewListItem->pxNext->pxPrevious = pxNewListItem;
    pxNewListItem->pxPrevious = pxIterator;
    pxIterator->pxNext = pxNewListItem;

    //记录当前链表项属于哪个链表
    pxNewListItem->pxContainer = pxList;

    ( pxList->uxNumberOfItems )++;//总数加1
}

看了源码不懂的话可以看一下图片
深入理解FreeRTOS_学习笔记(10.链表)_第3张图片

五、删除链表项

以下是删除链表的源码解析:

UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
	//获取删除的链表项所在哪个链表
    List_t * const pxList = pxItemToRemove->pxContainer;
	//删除链表项
    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
    pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
    mtCOVERAGE_TEST_DELAY();

    //如果当前pxIndex指向的是要删除的链表项,就将pxIndex左移,指向前一个链表项
    if( pxList->pxIndex == pxItemToRemove )
    {
        pxList->pxIndex = pxItemToRemove->pxPrevious;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    pxItemToRemove->pxContainer = NULL;//因为已经移除了,所以需要设置为NULL
    ( pxList->uxNumberOfItems )--;//总数减一

    return pxList->uxNumberOfItems;
}

删除链表项结点的操作比较简单就不讲解了

六、带参数的宏定义(链表操作)

如图所示,freertos具有非常多的链表宏操作
深入理解FreeRTOS_学习笔记(10.链表)_第4张图片

总结

freertos中对链表设定为双向链表,这是对数据结构中链表的深度理解,以及需要一定的C语言的代码能力。

你可能感兴趣的:(链表,数据结构,算法)