FreeRTOS链表实现
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)