FreeRTOS:1.通用链表分析

本文详细介绍了FreeRTOS中的通用链表实现,包括node结构、container关联方法,以及链表的初始化、插入、删除操作和相关宏函数。主要讨论了structnode_t和structcontainer的定义,以及如何通过不同的方式获取container地址。
摘要由CSDN通过智能技术生成

该系列记录学习FreeRTOS的源码的过程,基于rivencode-CSDN博客,以及FreeRTOS源码进行学习。

首先是FreeRTOS内核的通用链表实现。

通用链表实现

通用链表的作用:不同的结构体都可以使用通用的链表串联起来。

struct node_t{
	struct node_t *next;
}
struct container{
	int value;
	struct node_t node; // 包含了通用链表的结点
}

在已知node_t的地址,需要得到container的地址才能够访问container的其他成员。

实现方式有以下三种:(freertos使用的是方式3)

1、node作为container第一个成员

struct node_t{
	struct node_t *next;
}
struct container{
    struct node_t node; // 包含了通用链表的结点
	int value;
}

此时node_t成员的地址和container结构体的地址相同

2、node地址偏移反求container地址

在linux中常用这种方式,node放在container结构体中任意位置都可以通过node的地址计算container的地址。

先利用offsetof宏计算node结点在container结构体中偏移量,然后用node的地址减去偏移量得到container的地址,也就是contain_of宏。

以linux中的程序为例:

在这里插入图片描述

Linux 中的经典宏 container_of(ptr, type, member)剖析_( ((type *)0)->member )-CSDN博客

container_of中({})是GNU C的语法拓展,结果为最后一个语句的值
typeof是GNU C编译器的特有关键字。
linux中加入了 const typeof(((type*)0)->member)* __mptr = (ptr); 的目的在于引入类型检查,因为宏是预处理阶段的,无法保证类型安全,即参数ptr的地址类型和member的类型相同。

对于其他编译器,可以直接使用以下代码,不进行类型检查

#define container_of(ptr, type, member) ((type*)((char*)(ptr) - offsetof(type, member)))

3、node结点中保存container地址

最直接的方式就是在node结点中直接保存container的地址

struct node_t{
	struct node_t *next;
	void* container;
}
struct container{
	struct node_t node;
	int value;
}

FreeRTOS链表源码分析

CubeMX生成的工程文件为例,涉及源码如下:

Middlewares\Third_Party\FreeRTOS\Source\include\list.h

Middlewares\Third_Party\FreeRTOS\Source\list.c

相关结构体

1. 链表结构体

在这里插入图片描述

pxIndex为索引指针,是用于遍历链表的结点,是上一次调用listGET_OWNER_OF_NEXT_ENTRY时返回的链表项,表示当前pxIndex指向的链表项正在使用,那么pxIndex的previous结点此时是链表的末尾

2. 链表项结构体

  • 完整节点类型:

在这里插入图片描述
pxNext和pxPrevious共同维护了一个双向链表

pvOwner指向的是tskTaskControlBlock结构体,创建任务时就会创建一个任务结构体TCB,里面包含链表的结点(链表项),通过pvOwner就能找到TCB
在这里插入图片描述

通过pxContainer成员就能够找到这个结点所属的链表,以便访问链表的其他成员如链表结点个数,索引指针pxIndex。

  • 精简结点类型:

在这里插入图片描述

对于FreeRTOS的xList链表,本质上是一个双向链表,其中用一个精简的结点类型作为虚拟头结点,这个头结点没有任何含义,因此可以省去pvOwner和pxContainer这两项。链表中其他结点均为xLIST_ITEM类型。

对于xList、xMINI_LIST_ITEM、xLIST_ITEM结构体关系如下图:

在这里插入图片描述

初始化

1. 链表初始化

void vListInitialise( List_t * const pxList )
{
	// 链表还没使用,将索引指针pxIndex指向虚拟头节点,也就是xListEnd
	pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );			
    
    // 将链表的虚拟头结点的辅助排序值设置为最大,排序插入时保证是最后一个结点
	pxList->xListEnd.xItemValue = portMAX_DELAY;
	
    // 将头结点的pxNext、pxPrevious指向自己,表示此时链表为空
	pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );	
	pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );
	// 将链表中的结点个数设置为0
	pxList->uxNumberOfItems = ( UBaseType_t ) 0U;

	/* Write known values into the list if
	configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
	listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
	listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}

2. 链表项初始化

void vListInitialiseItem( ListItem_t * const pxItem )
{
	// 将链表项中指向所属链表的指针指向NULL,表示该结点不属于任何链表
	pxItem->pxContainer = NULL;

	/* Write known values into the list item if
	configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
	listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
	listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}

插入链表尾部

这里指的链表尾部,是索引指针pxIndex指向的结点位置的上一个位置,因为此时索引指针指向的链表项正在被使用,所以上一个结点是链表的尾部。

void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
    // 记录当前索引指针pxIndex指向的结点位置
	ListItem_t * const pxIndex = pxList->pxIndex;

	/* Only effective when configASSERT() is also defined, these tests may catch
	the list data structures being overwritten in memory.  They will not catch
	data errors caused by incorrect configuration or use of FreeRTOS. */
	listTEST_LIST_INTEGRITY( pxList );
	listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );

	// 在pxIndex指向的结点位置的上一个节点处,也就是pxIndex->pxPrevious后面插入这个结点pxNewListItem
	pxNewListItem->pxNext = pxIndex;
	pxNewListItem->pxPrevious = pxIndex->pxPrevious;

	/* Only used during decision coverage testing. */
	mtCOVERAGE_TEST_DELAY();

	pxIndex->pxPrevious->pxNext = pxNewListItem;
	pxIndex->pxPrevious = pxNewListItem;

	// 设置这个结点当前所在的链表为pxList
	pxNewListItem->pxContainer = pxList;
	// 链表pxList包含的节点数+1
	( pxList->uxNumberOfItems )++;
}

按序插入链表

利用给定节点的辅助排序值xItemValue来排序插入:遍历链表,找到第一个节点的排序值大于给定节点的排序值,在这个结点前面插入给定的结点。

void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
	ListItem_t *pxIterator;
    // 记录给定节点的辅助排序值xItemValue
	const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;

	listTEST_LIST_INTEGRITY( pxList );
	listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
	// 如果排序值为最大值,那么直接插入到虚拟头结点的上一个结点
	if( xValueOfInsertion == portMAX_DELAY )
	{
		pxIterator = pxList->xListEnd.pxPrevious;
	}
	else
	{	//从虚拟头结点开始遍历,寻找第一个节点的排序值大于给定节点的排序值,为插入位置
		for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) 
		{
			/* There is nothing to do here, just iterating to the wanted
			insertion position. */
		}
	}
    // 双向链表操作:插入节点
	pxNewListItem->pxNext = pxIterator->pxNext;
	pxNewListItem->pxNext->pxPrevious = pxNewListItem;
	pxNewListItem->pxPrevious = pxIterator;
	pxIterator->pxNext = pxNewListItem;
	// 更新节点所属的链表
	pxNewListItem->pxContainer = pxList;
    // 该链表包含的节点数+1
	( pxList->uxNumberOfItems )++;
}

删除链表项

UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
	// 得到要移除节点所属的链表
	List_t * const pxList = pxItemToRemove->pxContainer;
	// 从双向链表中删除该结点
	pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
	pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;

	/* Only used during decision coverage testing. */
	mtCOVERAGE_TEST_DELAY();

	/* 确保删除的链表项是当前正在使用的链表项,也就是pxIndex指向的链表项 */
	if( pxList->pxIndex == pxItemToRemove )
	{
		pxList->pxIndex = pxItemToRemove->pxPrevious;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
	// 将该结点指向所属链表的指针置为NULL,表示该结点不属于任何链表
	pxItemToRemove->pxContainer = NULL;
    // pxList链表的节点数-1
	( pxList->uxNumberOfItems )--;
	// 返回链表中结点个数
	return pxList->uxNumberOfItems;
}

链表操作相关的带参宏函数

FreeRTOS提供了一些带参宏函数,封装了这些节点操作。

// 设置节点的owner,也就是TCB结构体
#define listSET_LIST_ITEM_OWNER( pxListItem, pxOwner )		( ( pxListItem )->pvOwner = ( void * ) ( pxOwner ) )

// 获取节点的owner,也就是TCB结构体
#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 )	( ( ( pxList )->uxNumberOfItems == ( UBaseType_t ) 0 ) ? pdTRUE : pdFALSE )

// 获取链表的节点数
#define listCURRENT_LIST_LENGTH( pxList )	( ( pxList )->uxNumberOfItems )

在这里插入图片描述

  • 15
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值