**
文章大部分内容摘抄至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;
可以看到这个迷你的链表类型只有几个参数,重要的是指向前继,指向后续和项目值。
整个链表可以用以下这个图表示:
怎么将链表点插入链表尾部之中呢,如下图所示:
可以看出其实插入链表的尾部的话,并不是直接插入,而是插入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
}
以下是删除链表的源码解析:
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中对链表设定为双向链表,这是对数据结构中链表的深度理解,以及需要一定的C语言的代码能力。