freertos源码学习1----list实现

通过阅读freertos源码来学习链表这个数据结构,参考野火freertos教程

1. 链表在freertos源码的定义

1.1 xLIST_ITEM

直接在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的优点在于减少了对链表末尾节点的特殊处理,使代码更加简洁,同时也提高了链表的效率。

1.2 xLIST

/* 链表结构体定义 */
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实际上是一个孤立的节点,不包含任何有效数据。

结构体示意图如下:

freertos源码学习1----list实现_第1张图片

2. 函数实现

2.1 链表节点初始化

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

链表节点 ListItem_t 总共有 5 个成员,但是初始化的时候只需将pvContainer 初始化为空即可, 表示该节点还没有插入到任何链表。

freertos源码学习1----list实现_第2张图片

2.2 实现链表根节点

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.pxNextpxList->xListEnd.pxPrevious 都指向 pxList->xListEnd,表示链表为空,链表的头和尾都是哨兵节点 xListEnd
  • pxList->uxNumberOfItems 赋值为 0,表示当前链表中没有节点。

freertos源码学习1----list实现_第3张图片

2.3 节点插入链表尾部

/* 将节点插入到链表的尾部 */
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 )++;
}

这段代码实现了将一个链表节点插入到链表尾部的功能,具体做法如下:

  1. 定义一个指针 pxIndex 用于指向链表尾部的标记节点
  2. 修改新插入节点 pxNewListItem 的指针,使其指向 pxIndexpxIndex->pxPrevious
  3. 修改原尾部节点 pxIndex->pxPrevious 的指针,使其指向新插入节点 pxNewListItem
  4. 修改 pxIndex 的指针,使其指向新插入节点 pxNewListItem
  5. 设置新插入节点的 pvContainer 指针,指向所在的链表
  6. 增加链表节点计数器 uxNumberOfItems 的值

需要注意的是,这段代码中的链表是双向链表。因此,在修改节点指针的时候,需要同时修改其前驱和后继节点的指针。

freertos源码学习1----list实现_第4张图片

2.4 升序插入节点

/* 将节点按照升序排列插入到链表 */
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 )++;
}

这段代码实现了向一个双向链表中插入一个新的节点,并保证该节点插入后整个链表仍然保持有序性。具体来说,该函数接受两个参数:pxListpxNewListItem。其中pxList是一个指List_t结构体的指针,该结构体包含了链表的头尾节点;pxNewListItem是一个指向新要插入的节点的指针。

该函数首先获取要插入节点的排序辅助值xValueOfInsertion,然后寻找该节点要插入的位置。如果xValueOfInsertion等于portMAX_DELAY,则将节点插入到链表的末尾;否则,从链表头部开始循环,找到第一个满足pxIterator->pxNext->xItemValue <= xValueOfInsertion的节点,即为该节点要插入的位置。

接下来,将新节点插入到链表中。具体来说,先将新节点的指针指向要插入位置节点的下一个节点,然后将要插入位置节点的下一个节点的pxPrevious指针指向新节点,再将新节点的pxPrevious指针指向要插入位置节点,最后将要插入位置节点的pxNext指针指向新节点。插入完成后,更新新节点所在的链表和链表节点计数器。

freertos源码学习1----list实现_第5张图片

其实就是双向链表的插入,只不过加了一个优先值

2.5 将节点从链表删除

/* 将节点从链表中删除 */
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;
}

这段代码实现了从双向链表中删除一个节点的功能。具体来说,它做了以下几件事情:

  1. 获取待删除节点所在的链表。
  2. 通过修改待删除节点前一个节点和后一个节点的指针,将待删除节点从链表中删除。
  3. 如果链表中维护了一个索引指针,且该指针指向待删除节点,那么将该指针指向待删除节点的前一个节点。
  4. 将待删除节点的“所在链表”指针设置为NULL,表示该节点已经不再属于任何链表。
  5. 更新链表中节点的数量计数器。
  6. 返回链表中剩余节点的个数。

需要注意的是,这段代码没有对待删除节点本身进行内存释放的操作。如果该节点是通过动态内存分配得到的,需要在调用此函数后手动释放该节点的内存。

3.插入节点实验

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源码学习1----list实现_第6张图片
freertos源码学习1----list实现_第7张图片

4. 总结

总的来说,freertos的列表主要是采用主要是采用具有优先级的双向链表去完成,具体要怎么用,这种数据结构有什么用,需要我以后在运用的过程中去慢慢摸索了…

你可能感兴趣的:(freertos,学习,list,链表)