FreeRTOS专题二:列表(链表)

FreeRTOS的节点结构体定义如下:

/* 节点结构体定义 */
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;  /* 节点数据类型重定义 */

接下来分析,为什么要定义这样一个节点?

TickType_t xItemValue;             /* 辅助值,用于帮助节点做顺序排列 */	

// 关于TickType_t的定义
#if( configUSE_16_BIT_TICKS == 1 )
	typedef uint16_t TickType_t;
	#define portMAX_DELAY ( TickType_t ) 0xffff
#else
	typedef uint32_t TickType_t;
	#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
#endif	

TickType_t,顾名思义,就是系统滴答的类型。在stm32中,使用的系统滴答定时器为24位,因此要定义成32位类型(16位不够用),xItemValue中保存的是当前任务需要的延时值。在延时列表中,各任务根据延时大小(xItemValue值)按升序排列。

void * pvOwner;					   /* 指向拥有该节点的内核对象,通常是TCB */

保存当前任务的任务控制块,可以通过访问TCB从延时列表中恢复任务至就绪列表。

void *  pvContainer;		       /* 指向该节点所在的链表 */

通过这个变量,可以知道节点位于就绪列表、延时列表或其他。

***********************************************************************************************************************

如何初始化节点?直接让 pvContainer 这个指针为空(NULL),表示节点没有插入任何链表。

/* 节点初始化 */
void vListInitialiseItem(ListItem_t *const pxItem)
{
	pxItem->pvContainer = NULL;
}

链表结构体:

/* 链表结构体定义 */
typedef struct xLIST
{
	UBaseType_t uxNumberOfItems;    /* 链表节点计数器 */
	ListItem_t *  pxIndex;			/* 链表节点索引指针 */
	MiniListItem_t xListEnd;		/* 链表最后一个节点 */
} List_t;

分析链表结构体的成员:

UBaseType_t uxNumberOfItems;    /* 链表节点计数器 */

这个用于记录当前链表中有多少个任务(计数器)。

ListItem_t *  pxIndex;			/* 链表节点索引指针 */

这个每次都能指向当前链表中的下一个任务(1→2→3...)

MiniListItem_t xListEnd;		/* 链表最后一个节点 */

指向自身。

 

接下来是链表的初始化,代码看上去非常晦涩难懂,其实所做的工作就是,把索引指针指向最后一个节点,然后把最后一个节点的前指针和后指针都指向自身,并将节点计数器清零,表示链表为空(没有任务)。

/* 链表初始化 */
void vListInitialise(List_t * const pxList)
{
	/* 链表初始化,节点指向NULL,或结束地址 
	   FreeRTOS中是指向结束地址,并强制类型转换为指针 */
	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;
}

初始化完成后,根节点如下图所示:

接下来是链表的尾部插入新节点:

在链表初始化后,pxList->pxIndex指向最小mini节点(链表中没有节点时,mini节点的next和previous均指向自身),而此时如果插入一个节点1,就会变成mini节点的next指向节点1,而由于是双向链表,mini节点的previous也指向节点1。同理,节点1的next和previous都指向mini节点。

整个过程中,由于没有修改pxList->pxIndex,pxList->pxIndex一直指向mini节点。

但pxList->pxIndex->next指向了节点1,pxList->pxIndex->next->next指向节点2...

如果继续在尾部插入节点2,(先通过pxList->pxIndex获取mini节点)则节点2的next指向mini节点,而节点2的previous指向节点1,而节点1的next指向节点2,mini节点的previous指向节点2,整体的节点在不断往右生长,如图所示:

可以通过pxList->pxIndex来访问链表中的所有节点,怎么实现?

已经知道的是,pxList->pxIndex指向mini节点,如上图所示。如果ListItem_t * const pxIndex = pxList->pxIndex,那么pxIndex的next节点就是节点1(pxIndex->next),节点1的next就是节点2(pxIndex->next->next),这样就可以访问整个链表。

操作起来就是,pxIndex = pxList->pxIndex(此时指向mini最小节点),如果设置pxIndex = pxIndex->next,则pxIndex指向了节点1,继续pxIndex = pxIndex->next,则pxIndex指向了节点2,以此类推遍历列表。

插入节点的代码:

/**
  * @brief 将节点插入到链表的尾部
  * @param pxList 列表
  * @param pxNewListItem 新列表项
  */
void vListInsertEnd(List_t * const pxList, ListItem_t * const pxNewListItem)
{
	// 获取链表的尾部(mini节点)
	ListItem_t * const pxIndex = pxList->pxIndex;			

	// 新节点的下一个节点指向end
	pxNewListItem->pxNext = pxIndex;			
	// 新节点的上一个节点指向原end的上一个节点	
	pxNewListItem->pxPrevious = pxIndex->pxPrevious;
	// 上一个节点的下一个节点和新节点连接
	pxIndex->pxPrevious->pxNext = pxNewListItem;
	// 尾部节点的上一个节点和新节点连接
	pxIndex->pxPrevious = 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;
}

然后在主函数中进行测试

/* main.c */

#include "list.h"

/* 定义根节点 */
struct xLIST List_Test;

/* 定义3个节点 */
struct xLIST_ITEM List_Item1;
struct xLIST_ITEM List_Item2;
struct xLIST_ITEM List_Item3;

int main(void)
{
	// 根节点初始化
	vListInitialise(&List_Test);
	
	// 节点初始化
	vListInitialiseItem(&List_Item1);
	vListInitialiseItem(&List_Item2);
	vListInitialiseItem(&List_Item3);
	
	vListInsert(&List_Test, &List_Item1);
	vListInsert(&List_Test, &List_Item2);
	vListInsert(&List_Test, &List_Item3);
	
	for (; ;)
	{
		/* do nothing */
	}
}

测试结果如下:

 

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值