FreeRTOS笔记——链表数据结构

FreeRTOS链表实现

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

0 概述

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

1 关键结构体

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

1.1 链表基础知识

FreeRTOS使用双向链表,与数组通过开辟一段连续内存存储数据不同,链表通过把离散的数据(标准C类型或者是用户自定义结构体)链接成一个表,通过对节点的插入和删除操作来实现对数据的存取。
双向链表是一个圈,没有头尾之分,但是为了方便节点的插入和删除操作,需要人为规定一个根节点(root节点)。
FreeRTOS笔记——链表数据结构_第1张图片
如上图所示,如果链表为空,这只有一个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 链表操作

  • 根节点初始化
    FreeRTOS笔记——链表数据结构_第2张图片
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)

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