FreeRTOS—链表的实现

本文介绍了C语言中的链表基础知识,包括单向链表和双向链表,并详细阐述了FreeRTOS操作系统中链表的实现,包括节点结构体定义、初始化及插入删除操作。最后通过仿真验证了链表操作的正确性。

一.C语言链表

链表作为C语言中的一种基础的数据结构,在平时的程序中使用次数比较少,但在操作系统中链表中的使用次数比较多。链表的最大作用就是可以把离散的数据链接在一起,这和数组是不一样的,数组的存储空间是一段连续的内存,而链表可以把内存中不连续的数据联系起来。学习链表主要是学习链表和节点。链表和节点之间的关系就像是晾衣架和钩子的关系:
在这里插入图片描述
链表就像是最上面的挂钩,节点就像是下面的小钩子,通过上面的钩子可以找到所有的小钩子,节点之间是首位相连的,每个节点上可以挂载数据,这样的话一个链表就可以把许多数据联系在一起。

1.单向链表

单向链表节点本身必须包含一个节点指针,用于指向下一个节点,当然节点中还可以携带其他的数据。节点是一个自定义类型的数据结构,在节点中可以有单个的数据、数组、指针数据或结构体数据等。
例如:

struct node
{
 struct node *next; /* 指向链表的下一个节点 */
 
 /* 节点携带的数据可以是如下内容 */
 char data1; /* 单个的数据 */
 unsigned char array[]; /* 数组 */
 unsigned long *prt /* 指针数据 */
 struct userstruct data2; /* 自定义结构体类型数据 */
 /* 其他数据类型 */
 }

在单向链表中,最后一个节点的指针指向第一个节点,代表链表是循环的,如图所示:
在这里插入图片描述

2.双向链表

双向链表和单向链表的区别就是:双向链表中有俩个节点指针,分别指向前一个节点和后一个节点,其余部分和单向链表一样。
双向链表的结构如示:
在这里插入图片描述

3.链表与数组

链表是通过节点把离散的数据链接成一个表,通过对节点的插入和删除操作从而实现对数据的存取。而数组是通过开辟一段连续的内存来存储数据,这是数组和链表最大的区别。 数组的每个成员对应链表的节点,成员和节点的数据类型可以是标准的 C 类型或者是用户自定义的结构体。数组有起始地址和结束地址,而链表是一个圈,没有头和尾之分,但是为了方便节点的插入和删除操作会人为的规定一个根节点。

二.FreeRTOS中链表实现

1.定义节点结构体

定义链表节点数据结构如下:

/* 定义节点的数据结构 */
struct xLIST_ITEM
{
	/* 辅助值,相当于节点的序号 */
	TickType_t xItemValue;
	/* 指向下一个节点 */
	struct xLIST_ITEM * pxNext;
	/* 指向前一个节点 */
	struct xLIST_ITEM * pxPrevious;
	/* 指向拥有该节点的内核对象,通常为TCB */
	void * pvOwner;
	/* 指向该节点所在的链表 */
	void * pvContainer;
};
/* 重定义节点数据类型 */
typedef struct xLIST_ITEM ListItem_t;

2.定义精简节点结构体

/* 定义链表精简节点结构体 */
struct xMINI_LIST_ITEM
{
	/* 辅助值,帮助节点做升序排序 */
	TickType_t xItemValue;
	/* 指向链表下一个节点 */
	struct xLIST_ITEM * pxNext;
	/* 指向节点前一个节点 */
	struct xLIST_ITEM * pxPrevious;
};
/* 重定义精简节点结构体 */
typedef struct xMINI_LIST_ITEM MiniListItem_t;

3.定义根节点结构体

/* 定义节点链表(根节点)的数据结构 */
typedef struct xLIST
{
	/* 链表节点计数器,记录链表中节点的个数 */
	UBaseType_t uxNumberOfItemsl;
	/* 链表节点索引指针,用于遍历节点 */
	ListItem_t * pxIndex;
	/* 链表最后一个节点,也是第一个节点,此节点数据结构是精简结构 */
	MiniListItem_t xListEnd;
}List_t;

4.节点初始化

/* 链表节点初始化 */
void vListInitialiseItem( ListItem_t * const pxItem )
{
	/* 将节点指向的链表为空,表示节点还没有插入任何链表 */
	pxItem->pvContainer = NULL;
}

5.根节点初始化

/* 链表根节点初始化 */
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 );
	/* 初始化链表节点计数器为0,表示链表为空 */
	pxList->uxNumberOfItemsl = ( UBaseType_t ) 0;
}

6.将节点插入到链表尾部

/* 将节点插入到链表尾部,即插入到最后一个节点的前面,充当最后节点 */
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->uxNumberOfItemsl )++;
}

7.将节点插入到固定位置

/* 将节点按照升序插入到链表中,插在序号节点前面 */
/* 比如插入节点的序号为7,代表要插在链表原来的6与7之间,成为新的7号 */
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
	{
		/* 直到迭代节点是6号节点 */
		for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd );
					pxIterator->pxNext->xItemValue <= xValueOfInsertion;
					pxIterator = pxIterator->pxNext)
		{
			/* 什么都不做,不断迭代只为寻找节点要插入的位置 */
		}
	}
	
	/* 将节点插入到迭代节点后面,即插在6号节点后面 */
	pxNewListItem->pxNext = pxIterator->pxNext;
	pxNewListItem->pxNext->pxPrevious = pxNewListItem;
	pxNewListItem->pxPrevious = pxIterator;
	pxIterator->pxNext = pxNewListItem;
	
	/* 将插入的节点归入链表中 */
	pxNewListItem->pvContainer = ( void * ) pxList;
	/* 链表中节点数加一 */
	( pxList->uxNumberOfItemsl )++;
}

8.删除节点

/* 将节点从链表删除,并返回链表中的剩余节点个数 */
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;
	
	/* 如果删除的节点恰好是节点索引指针所指,调节链表的节点索引指针 */
	if( pxList->pxIndex == pxItemToRemove )
	{
		pxList->pxIndex = pxItemToRemove->pxPrevious;
	}
	/* 初始化删除后的节点,表示未插入任何链表 */
	pxItemToRemove->pvContainer = NULL;
	/* 链表中的节点数目减一 */
	( pxList->uxNumberOfItemsl )--;
	/* 返回链表中剩余的节点个数 */
	return ( pxList->uxNumberOfItemsl );
}

三.仿真验证

运行下面的代码,利用仿真检验链表节点之间的关系是否正确,创建一个根节点、三个节点,将三个节点顺序插入链表中。

#include "list.h"

/* 一个根节点 */
struct xLIST        List_Test;
/* 三个节点 */
struct xLIST_ITEM   List_Item_1;
struct xLIST_ITEM   List_Item_2;
struct xLIST_ITEM   List_Item_3;


int main(void)
{
	/* 初始化根节点 */
	vListInitialise( & List_Test );
	
	/* 初始化节点 */
	vListInitialiseItem( & List_Item_1 );
	List_Item_1.xItemValue = 1;
	vListInitialiseItem( & List_Item_2 );
	List_Item_2.xItemValue = 2;
	vListInitialiseItem( & List_Item_3 );
	List_Item_3.xItemValue = 3;

	/* 将节点插入到链表中 */
	vListInsert( & List_Test, & List_Item_1 );
	vListInsert( & List_Test, & List_Item_2 );
	vListInsert( & List_Test, & List_Item_3 );

	while( 1 )
	{
		;
	}
}

仿真结果如示:

在这里插入图片描述
从结果中看出,各节点之间的对应关系是正确的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值