通过阅读freertos源码来学习链表这个数据结构,参考野火freertos教程
直接在
list.h
找到xLIST_ITEM
这个结构体
/* 节点结构体定义 */
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; /* 节点数据类型重定义 */
/* mini节点结构体定义,作为双向链表的结尾
因为双向链表是首尾相连的,头即是尾,尾即是头 */
struct xMINI_LIST_ITEM
{
TickType_t xItemValue; /* 辅助值,用于帮助节点做升序排列 */
struct xLIST_ITEM * pxNext; /* 指向链表下一个节点 */
struct xLIST_ITEM * pxPrevious; /* 指向链表前一个节点 */
};
typedef struct xMINI_LIST_ITEM MiniListItem_t; /* 最小节点数据类型重定义 */
xItemValue
:xItemValue是FreeRTOS操作系统中用于在优先级队列中排序的数据项的一个字段。它可以是任何数据类型,通常是数字或指针,表示数据项的优先级或权重。在优先级队列中,xItemValue越小的数据项具有越高的优先级,因此优先级队列可以按照xItemValue的大小对数据项进行排序,以便快速找到具有最高优先级的数据项。configLIST_VOLATILE
:configLIST_VOLATILE是FreeRTOS操作系统中的一个配置选项,它指定任务列表数据结构是放置在易失性内存还是非易失性内存中。如果设置为1,则任务列表放置在易失性内存中,这样速度更快但持久性较差。如果设置为0,则任务列表放置在非易失性内存中,这样速度较慢但持久性更好。TickType_t
:TickType_t是FreeRTOS操作系统中用于表示时间的数据类型,通常定义为32位无符号整数。它表示系统运行的时钟节拍数,可以用于测量时间间隔、延迟时间和任务调度。TickType_t的值可以通过调用vTaskSetTimeOutState()xTaskCheckForTimeOut()等函数来更新和比较,以实现超时等时间相关功能。在FreeRTOS配置文件中以通过configTICK_RATE_HZ宏定义来设置系统时钟的节拍频率。pxNext
:指向下一个节点pxPrevious
:指向上一个节点pvOwner
:pvOwner是FreeRTOS操作系统中用于互斥量、信号量、队列等内核对象的一个字段,它表示当前拥有该内核对象的任务的句柄或指针。在多任务环境下,任务需要通过获取内核对象的拥有权来访问共享资源或同步操作。当一个任务获得了内核对象的拥有权时,pvOwner字段会被设置为该任务的句柄或指针,其他任务需要等待该任务释放内核对象的拥有权才能访问该对象。通过pvOwner字段,内核可以跟踪内核对象的拥有权,以确保同一时间只有一个任务可以访问该对象。用于指向该节点的拥有者, 即该节点内嵌在哪个数据结构中, 属于哪个数据结构的一个成员pxContainer
:pxContainer是FreeRTOS操作系统中用于内存管理的数据结构之一,它表示内存块的容器或者说是内存块所属的内存池。在内存管理中,内存块通常分配给任务或驱动程序使用,而内存池则用于管理这些内存块的分配和释放。每个内存块都包含一个指向其所属容器的指针pxContainer,通过该指针可以将内存块还回正确的内存池。pxContainer通常包含内存池的状态信息,如内存池的起始地址、内存块的大小、空闲内存块的数量等,以便内存管理器能够有效地管理内存分配和释放。typedef struct xLIST_ITEM ListItem_t;
:typedef struct xLIST_ITEM ListItem_t; 是FreeRTOS操作系统中用于定义双向链表节点的结构体声明。xLIST_ITEM结构体包含了一个指向前一个节点的指针pxPrevious、一个指向后一个节点的指针pxNext,以及一个用于排序的字段xItemValue。双向链表是FreeRTOS中许多数据结构的基础,如任务列表、事件列表、定时器列表等,通过双向链表可以高效地进行插入、删除和遍历操作。ListItem_t是对xLIST_ITEM结构体的重命名,方便用户使用和提高代码可读性。我们可以看到会发现又有一个
xMINI_LIST_ITEM
的结构体,它是干什么的呢?
xMINI_LIST_ITEM
是一个辅助结构体,通常与xLIST
一起使用。它的作用是作为xLIST
的最后一个节点,可以减少代码的复杂性,使链表的插入和删除操作更加高效。在xLIST中,节点的插入和删除需要处理头部和尾部的情况,而
xMINI_LIST_ITEM
节点的引入可以避免这种情况。它作为链表的结尾,可以通过pxPrevious
指针链接到链表的最后一个节点,使得插入和删除节点时,不需要特别处理链表的末尾节点。
xMINI_LIST_ITEM
的优点在于减少了对链表末尾节点的特殊处理,使代码更加简洁,同时也提高了链表的效率。
/* 链表结构体定义 */
typedef struct xLIST
{
UBaseType_t uxNumberOfItems; /* 链表节点计数器 */
ListItem_t * pxIndex; /* 链表节点索引指针 */
MiniListItem_t xListEnd; /* 链表最后一个节点 */
} List_t;
这段源码定义了一个名为
List_t
的结构体,表示一个链表。该结构体包含三个成员变量:
uxNumberOfItems
: 记录链表中节点的数量。pxIndex
: 链表节点的索引指针。xListEnd
: 链表的最后一个节点,它是一个MiniListItem_t
类型的变量。
MiniListItem_t
类型是一个小型的链表节点,它只包含一个pxNext
指针,指向下一个节点。而ListItem_t
类型则是一个标准的链表节点,除了pxNext
指针之外,还包含了pxPrevious
指针和一个pvOwner
指针。该结构体用于表示一个链表,其中
uxNumberOfItems
用于记录链表中节点的数量,pxIndex
是一个指向链表中第一个节点的指针。xListEnd
是一个特殊节点,它用于表示链表的结尾,其pxNext
指针为空指针,因此xListEnd
实际上是一个孤立的节点,不包含任何有效数据。
结构体示意图如下:
void vListInitialistItem( ListItem_t * const pxItem)
{
/* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */
pxItem->pvContainer = NULL;
}
链表节点 ListItem_t 总共有 5 个成员,但是初始化的时候只需将pvContainer 初始化为空即可, 表示该节点还没有插入到任何链表。
void vListInitialise(List_t * const pxList)
{
pxList->pxIndex = (ListItem_t *)&(pxList->xListEnd);
pxList->xListEnd.xItemValue = portMAX_DELAY;
pxList->xListEnd.pxNext = (ListItem_t *)&(pxList->xListEnd);
pxList->xListEnd.pxPrevious = (ListItem_t *)&(pxList->xListEnd);
pxList->uxNumberOfItems = (UBaseType_t)0U;
}
这段代码定义了一个名为
vListInitialise
的函数,该函数用于初始化一个名为pxList
的链表。链表结构使用了一个双向循环链表实现,其中包含以下成员变量:
pxIndex
:指向链表中第一个可用节点的指针。xListEnd
:链表中最后一个节点,也是一个哨兵节点,其值为portMAX_DELAY
,表示链表中没有更多的节点。uxNumberOfItems
:链表中的节点数量。函数的具体操作如下:
- 将
pxList->pxIndex
指向pxList->xListEnd
的地址,表示当前链表为空,没有可用节点。- 将
pxList->xListEnd.xItemValue
赋值为portMAX_DELAY
,表示链表中没有更多的节点。- 将
pxList->xListEnd.pxNext
和pxList->xListEnd.pxPrevious
都指向pxList->xListEnd
,表示链表为空,链表的头和尾都是哨兵节点xListEnd
。- 将
pxList->uxNumberOfItems
赋值为0
,表示当前链表中没有节点。
/* 将节点插入到链表的尾部 */
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 )++;
}
这段代码实现了将一个链表节点插入到链表尾部的功能,具体做法如下:
- 定义一个指针
pxIndex
用于指向链表尾部的标记节点- 修改新插入节点
pxNewListItem
的指针,使其指向pxIndex
和pxIndex->pxPrevious
- 修改原尾部节点
pxIndex->pxPrevious
的指针,使其指向新插入节点pxNewListItem
- 修改
pxIndex
的指针,使其指向新插入节点pxNewListItem
- 设置新插入节点的
pvContainer
指针,指向所在的链表- 增加链表节点计数器
uxNumberOfItems
的值需要注意的是,这段代码中的链表是双向链表。因此,在修改节点指针的时候,需要同时修改其前驱和后继节点的指针。
/* 将节点按照升序排列插入到链表 */
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
/* 获取节点的排序辅助值 */
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;
pxNewListItem->pxPrevious = pxIterator;
pxIterator->pxNext = pxNewListItem;
/* 记住该节点所在的链表 */
pxNewListItem->pvContainer = ( void * ) pxList;
/* 链表节点计数器++ */
( pxList->uxNumberOfItems )++;
}
这段代码实现了向一个双向链表中插入一个新的节点,并保证该节点插入后整个链表仍然
保持有序性
。具体来说,该函数接受两个参数:pxList
和pxNewListItem
。其中pxList
是一个指List_t
结构体的指针,该结构体包含了链表的头尾节点;pxNewListItem
是一个指向新要插入的节点的指针。该函数首先获取要插入节点的排序辅助值
xValueOfInsertion
,然后寻找该节点要插入的位置。如果xValueOfInsertion
等于portMAX_DELAY
,则将节点插入到链表的末尾;否则,从链表头部开始循环,找到第一个满足pxIterator->pxNext->xItemValue <= xValueOfInsertion
的节点,即为该节点要插入的位置。接下来,将新节点插入到链表中。具体来说,先将新节点的指针指向要插入位置节点的下一个节点,然后将要插入位置节点的下一个节点的
pxPrevious
指针指向新节点,再将新节点的pxPrevious
指针指向要插入位置节点,最后将要插入位置节点的pxNext指针指向新节点。插入完成后,更新新节点所在的链表和链表节点计数器。
其实就是双向链表的插入,只不过加了一个优先值
/* 将节点从链表中删除 */
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
/* 获取节点所在的链表 */
List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;
pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
/* Make sure the index is left pointing to a valid item. */
if( pxList->pxIndex == pxItemToRemove )
{
pxList->pxIndex = pxItemToRemove->pxPrevious;
}
/* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */
pxItemToRemove->pvContainer = NULL;
/* 链表节点计数器-- */
( pxList->uxNumberOfItems )--;
/* 返回链表中剩余节点的个数 */
return pxList->uxNumberOfItems;
}
这段代码实现了从双向链表中删除一个节点的功能。具体来说,它做了以下几件事情:
- 获取待删除节点所在的链表。
- 通过修改待删除节点前一个节点和后一个节点的指针,将待删除节点从链表中删除。
- 如果链表中维护了一个索引指针,且该指针指向待删除节点,那么将该指针指向待删除节点的前一个节点。
- 将待删除节点的“所在链表”指针设置为NULL,表示该节点已经不再属于任何链表。
- 更新链表中节点的数量计数器。
- 返回链表中剩余节点的个数。
需要注意的是,这段代码没有对待删除节点本身进行内存释放的操作。如果该节点是通过动态内存分配得到的,需要在调用此函数后手动释放该节点的内存。
main.c
#include "list.h"
// 定义根节点
List_t list_test;
// 定义节点
struct xLIST_ITEM node1;
struct xLIST_ITEM node2;
struct xLIST_ITEM node3;
int main()
{
// 根节点初始化
vListInitialise(&list_test);
// 节点初始化
vListInitialistItem(&node1);
vListInitialistItem(&node2);
vListInitialistItem(&node3);
node1.xItemValue = 1;
node2.xItemValue = 2;
node3.xItemValue = 3;
vListInsert(&list_test, &node1);
vListInsert(&list_test, &node2);
vListInsert(&list_test, &node3);
for(;;)
{
}
}
list.c
#include "list.h"
void vListInitialistItem( ListItem_t * const pxItem)
{
/* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */
pxItem->pvContainer = NULL;
}
void vListInitialise(List_t * const pxList)
{
pxList->pxIndex = (ListItem_t *) & (pxList->xListEnd);
pxList->xListEnd.xItemValue = portMAX_DELAY;
pxList->xListEnd.pxNext = (ListItem_t *)&(pxList->xListEnd);
pxList->xListEnd.pxPrevious = (ListItem_t *)&(pxList->xListEnd);
pxList->uxNumberOfItems = (UBaseType_t)0U;
}
/* 将节点插入到链表的尾部 */
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 )++;
}
/* 将节点按照升序排列插入到链表 */
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
/* 获取节点的排序辅助值 */
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;
pxNewListItem->pxPrevious = pxIterator;
pxIterator->pxNext = pxNewListItem;
/* 记住该节点所在的链表 */
pxNewListItem->pvContainer = ( void * ) pxList;
/* 链表节点计数器++ */
( pxList->uxNumberOfItems )++;
}
/* 将节点从链表中删除 */
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
/* 获取节点所在的链表 */
List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;
pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
/* Make sure the index is left pointing to a valid item. */
if( pxList->pxIndex == pxItemToRemove )
{
pxList->pxIndex = pxItemToRemove->pxPrevious;
}
/* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */
pxItemToRemove->pvContainer = NULL;
/* 链表节点计数器-- */
( pxList->uxNumberOfItems )--;
/* 返回链表中剩余节点的个数 */
return pxList->uxNumberOfItems;
}
list.h
#ifndef LIST_H
#define LIST_H
#include "portmacro.h"
/* 节点结构体定义 */
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; /* 节点数据类型重定义 */
/* mini节点结构体定义,作为双向链表的结尾
因为双向链表是首尾相连的,头即是尾,尾即是头 */
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 xListEnd; /* 链表最后一个节点 */
} List_t;
void vListInitialistItem( ListItem_t * const pxItem);
void vListInitialise(List_t * const pxList);
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem );
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem );
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove );
#endif
总的来说,freertos的列表主要是采用主要是采用具有优先级的双向链表去完成,具体要怎么用,这种数据结构有什么用,需要我以后在运用的过程中去慢慢摸索了…