FreeRTOS笔记——链表数据结构

    技术2025-07-16  5

    FreeRTOS链表实现

    0 概述1 关键结构体1.1 链表基础知识1.2 链表数据结构1.3 链表操作

    0 概述

    部分内容参考野火的FreeRTOS相关开发资料,在此做一个学习记录总结。

    1 关键结构体

    FreeRTOS源码实现中存在很多链表相关操作,理解链表相关操作对掌握FreeRTOS实现原理至关重要。

    1.1 链表基础知识

    FreeRTOS使用双向链表,与数组通过开辟一段连续内存存储数据不同,链表通过把离散的数据(标准C类型或者是用户自定义结构体)链接成一个表,通过对节点的插入和删除操作来实现对数据的存取。 双向链表是一个圈,没有头尾之分,但是为了方便节点的插入和删除操作,需要人为规定一个根节点(root节点)。 如上图所示,如果链表为空,这只有一个root节点,node_num为0,next和previous均指向自身。 为了有助于理解后面相关初始化以及插入删除操作,此处需要记住下面两点: 1)root_node->next指向链表的第一项node1(或自身,如果是空链表) 2)root_node->previous指向链表的最后一项noden(或自身,如果是空链表)

    1.2 链表数据结构

    链表数据节点 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; 链表根节点 struct xMIN_LIST_ITEM { TickType_t xItemValue; /* 辅助值,用于帮助节点做顺序排列 */ struct xLIST_ITEM *pxNext; /* 指向链表下一个节点 */ struct xLIST_ITEM *pxPrevious; /* 指向链表前一个节点 */ }; 链表本身 typedef struct xLIST { UBaseType_t uxNumberOfItems; /* 链表节点计数器 */ ListItem_t *pxIndex; /* 链表节点索引指针,初始化后指向链表的最后一个节点或者说是头部节点,即下面的xListEnd节点,称之为root根节点; pxIndex->pxNext是链表中第一个有效节点(或自身,即空链表),pxIndex->pxPrevious是链表中最后一个有效节点(或自身,即空链表); 任务调度过程中,节点索引会动态变化,指向不同人物的TCB */ MiniListItem_t xListEnd; /* 链表中包含最大xItemValue的节点,位于链表末端,也可认为是头部,用作标记 marker */ } List_t;

    1.3 链表操作

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

    上面关键的一点是pxList->pxIndex指向根节点,这样后续插入删除操作便可以有了一个其实参考点。

    节点初始化 这里的节点指的是除根节点外的有效挂载数据的节点,初始化就是告诉程序,该节点未挂到任何链表上,实现代码如下: void vListInitialiseItem(ListItem_t * const pxItem) { /* 初始化该节点所在的链表为空,表示该节点没有插入任何链表,没人能管得了我了!! */ pxItem->pvContainer = NULL; } 插入节点至尾部 从前分析可知,链表的根节点,即xListEnd->pxPrevious指向的就是原链表的尾部节点,节点插入尾部时,就是要让新的节点跟在原来尾部节点之后,可以理解为在原尾部节点与根节点之间插入一个节点,代码实现如下: void vListInsertEnd(List_t * const pxList, ListItem_t * const pxNewListItem) { ListItem_t * const pxIndex = pxList->pxIndex; /* 链表索引节点,链表初始化后指向根节点*/ pxNewListItem->pxNext = pxIndex; /* 先插入尾部的节点的pxNext指向根节点 */ pxNewListItem->pxPrevious = pxIndex->pxPrevious; /* pxIndex->pxPrevious 是原来的链表尾部节点 */ pxIndex->pxPrevious->pxNext = pxNewListItem; pxIndex->pxPrevious = pxNewListItem; //pxIndex->pxNext 指向不变化 /* 记住该节点所在的链表 */ pxNewListItem->pvContainer = (void *)pxList; /* 链表节点计数器++ */ (pxList->uxNumberOfItems)++; } 升序插入节点 根据节点的xItemValue辅助值对接点进行排序,如果xItemValue相同,就根据插入顺序排列了。 插入后一定要记得更新链表中节点数据计数器pxList->uxNumberOfItems和新插入节点的pxNewListItem->pvContainer(否则节点就是未挂载在任何链表上)。 void vListInsert(List_t * const pxList, ListItem_t * const pxNewListItem) { /* * 记录辅助值比 pxNewListItem-> xItemValue 小的的列表项 ItemX , * ItemX再往后的列表项 ItemX++ 的辅助值会大于或等于 pxNewListItem-> xItemValue * 插入的位置即 ItemX 之后 */ ListItem_t *pxIterator = NULL; /* 获取节点排序辅助值 */ 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; pxNewListItem->pxNext->pxPrevious = pxNewListItem; //或者 pxIterator->pxNext->pxPrevious = pxNewListItem pxNewListItem->pxPrevious = pxIterator; pxIterator->pxNext = pxNewListItem; /* 记住该节点所在的链表 */ pxNewListItem->pvContainer = (void *)pxList; /* 链表节点计数器++ */ (pxList->uxNumberOfItems)++; } 删除节点 删除节点后需要清除删除节点的pvContain指向,做好已删除节点原pxNext和pxPrevious指向节点的衔接工作,并且链表节点数目要相应自减。 其中特别注意的是,当删除最后一个节点时,需要调整节点索引指针,即让根节点指向自己,即链表初始化后为空的状态。 UBaseType_t uxListRemove(ListItem_t * const pxItemToRemove) { /* 获取节点所在列表 */ List_t * const pxList = pxItemToRemove->pvContainer; /* 类似离职后,做好前上级下级的交接工作 */ pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext; pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious; /* 在使用链表进行任务调度的过程中节点索引会变化, 如找到当前优先级最高的任务的TCB时就调用了listGET_OWNER_OF_NEXT_ENTRY 来不断遍历节点, 因此,我如果删除的节点刚好是最终pxIndex停留的节点,则需要重新给pxIndex进行赋值, 不管是待删除节点的上级还是下级都可以,通过其中一个,总能找到其他人 */ if( pxList->pxIndex == pxItemToRemove ) { pxList->pxIndex = pxItemToRemove->pxPrevious; // 或者 pxItemToRemove->pxNext,反正都能通过它们遍历其他节点 } /* 初始化节点所在的链表为空,表示节点还没有插入任何链表,没人能管得了我了!! */ 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) /* 获取下一个节点的Onwer,即 TCB */ #define listGET_OWNER_OF_NEXT_ENTRY(pxTCB, pxList) \ { \ List_t * const pxConstList = (pxList); \ /* Increment the index to the next item and return the item, */ \ /* ensuring we don't return the marker used at the end of the list */ \ (pxConstList)->pxIndex = (pxConstList)->pxIndex->pxNext; \ if((void *)(pxConstList)->pxIndex == (void *)&((pxConstList)->xListEnd)) \ { \ (pxConstList)->pxIndex = (pxConstList)->pxIndex->pxNext; \ } \ /* 获取节点的OWNER,即TCB */ \ (pxTCB) = (pxConstList)->pxIndex->pvOwner; \ } #define listGET_OWNER_OF_HEAD_ENTRY(pxList) ((&((pxList)->xListEnd))->pxNext->pvOwner)
    Processed: 0.016, SQL: 9