FreeRTOS列表和列表项源码分析

FreeRTOS中一个核心的数据结构就是列表和列表项。剖析FreeRTOS源码的一个必要条件就是掌握列表和列表项。列表和列表项属于数据结构的知识。在数据结构课程中所学的链表和FreeRTOS中的列表在实质上是一个东西。如果学过数据结构中的链表再看FreeRTOS中列表和列表项的知识那就不费吹灰之力了。废话不多说我们来看代码吧。
我这里是在Visual studio开发环境里测试代码。去掉了一些非必要性的代码(比如去掉了列表完整性检查),完全不影响剖析源码。
列表项数据类型

//定义列表项
struct xLIST_ITEM
{
	volatile uint32_t xItemValue;//列表项辅助值 列表排序使用
	struct xLIST_ITEM * volatile pxNext;//指向下一个列表项的指针
	struct xLIST_ITEM * volatile pxPrevious;//指向前一个列表项的指针
	void * pvOwner;//指向拥有该内核对象的列表项(通常是TCB)
	struct xLIST * volatile pxContainer;//指向该列表项所属的列表
};
typedef struct xLIST_ITEM ListItem_t;

FreeRTOS列表和列表项源码分析_第1张图片
迷你列表项数据类型

//定义迷你列表项
struct xMINI_LIST_ITEM
{
	volatile uint32_t xItemValue;//列表项辅助值 列表排序使用
	struct xLIST_ITEM * volatile pxNext;//指向下一个列表项的指针
	struct xLIST_ITEM * volatile pxPrevious;//指向前一个列表项的指针
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

FreeRTOS列表和列表项源码分析_第2张图片
迷你列表项比列表项少了pvOwner和pxContainer成员变量。迷你列表项就是列表项的阉割版。为什么不直接用列表项而要用迷你列表项呢。因为迷你列表项比列表项省内存。列表的根节点(头节点)就是迷你列表项。这个根节点没有实际意义,只是为了方便列表操作而存在。

列表数据类型

//定义列表
typedef struct xLIST
{
	volatile uint32_t uxNumberOfItems;//列表中列表项的个数
	ListItem_t * volatile pxIndex;//遍历列表项的指针
	MiniListItem_t xListEnd;//列表中的头节点(根节点)只是为了方便列表的操作
}List_t;

FreeRTOS列表和列表项源码分析_第3张图片列表项初始化函数

//列表项初始化函数
void vListInitialiseItem(ListItem_t * const pxItem)
{
	pxItem->pxContainer = NULL;
}

列表项成员pxContainer表示该列表项属于哪条列表,在FreeRTOS内核中就是任务的状态列表项或事件列表项挂载到哪条列表中。

列表初始化函数

//列表初始化函数
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 = 0;
}

列表初始化略显复杂,初始化完成以后如下图

FreeRTOS列表和列表项源码分析_第4张图片
此时就是一条空列表。
列表末尾插入函数

//列表项末尾插入到列表
//FreeRTOS规定列表的pxIndex指针指向的是头,列表是环形的
//所以末尾插入就是插入到pxIndex前边
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->pxContainer = pxList;//列表项属于哪条列表

	(pxList->uxNumberOfItems)++;//列表中列表项的个数++
}

注意FreeRTOS约定列表的第一个节点是列表成员pxIndex指向的节点,那么列表末尾插入肯定是插入到pxIndex的前一个节点的位置。
列表插入函数

//列表项插入函数
//根据列表项的值决定要插入的位置
void vListInsert(List_t * const pxList, ListItem_t * const pxNewListItem)
{
	ListItem_t * pxIterator;//定义一个指向列表项的指针
	const uint32_t xValueOfInsertion = pxNewListItem->xItemValue;//获取待插入列表项的值
	if (xValueOfInsertion == portMAX_DELAY)
	{
		pxIterator = pxList->xListEnd.pxPrevious;//获取根节点前一个节点的地址
	}
	else
	{
		/*
		看出没?是不是和在一个有序数组里找一个位置很类似呀,我没学数组结构之前,别人我说数组和链表很相似,
		那时候的我听到这句话是懵逼的,觉得链表和数组没啥关系。直到学了数据结构才明白了这句话的意义。
		for(i = 0;value < buffer[i];i++)
		这里体现出了泛型的思想,数据的存储方式不一样,操作方式也一样(操作方式广义上讲是一样的)
		*/		
		for (pxIterator = (ListItem_t *)&(pxList->xListEnd); pxIterator->pxNext->xItemValue <= xValueOfInsertion;pxIterator = pxIterator->pxNext)
		{

		}
	}
	//新的节点插到pxIterator所指节点的后边
	pxNewListItem->pxNext = pxIterator->pxNext;
	pxNewListItem->pxNext->pxPrevious = pxNewListItem;
	pxNewListItem->pxPrevious = pxIterator;
	pxIterator->pxNext = pxNewListItem;

	pxNewListItem->pxContainer = pxList;
	(pxList->uxNumberOfItems)++;
}

列表删除函数

//删除一个列表项
uint32_t uxListRemove(ListItem_t * const pxItemToRemove)
{
	List_t * const pxList = pxItemToRemove->pxContainer;//获取要删除列表项所在的列表
	pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
	pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
	if (pxList->pxIndex == pxItemToRemove)//如果遍历指针恰好指向被删除的列表项
	{
		pxList->pxIndex = pxItemToRemove->pxPrevious;//那么遍历指针指向被删除节点的前一个节点
	}
	pxItemToRemove->pxContainer = NULL;//删除的列表项不属于任何列表
	(pxList->uxNumberOfItems)--;//列表中的列表项个数减减
	return pxList->uxNumberOfItems;//返回列表中列表项的个数
}

注意FreeRTOS链表删除函数在删除时并没有判断列表是否为空列表。所以在调用删除函数时,必须保证该列表项是在列表中的。
遍历列表函数
这个函数是我自己实现的,官方源码中并没有。是在学习列表时用来输出列表,观察列表。这个函数相对来说就很简单了。

//遍历列表
void traverse_list(List_t * const pxList)
{
	const ListItem_t * ptemp = pxList->xListEnd.pxNext;//获取根节点的下一个节点的地址,

	while (ptemp != (ListItem_t *)&(pxList->xListEnd))//如果遍历到的节点不是根节点
	{
		printf("%d\n",ptemp->xItemValue);//输出该节点的值(此处是输出,也可以做一些其它操作,如将其值赋值为0)
		ptemp = ptemp->pxNext;//指向节点的指针向后移动
	}
}

在列表插入函数中提到了泛型思想,那这个遍历我们也用同样的思维去实现它。感受泛型的威力。

//遍历列表
void traverse_list(List_t * const pxList)
{
	for (const ListItem_t * ptemp = pxList->xListEnd.pxNext; ptemp != (ListItem_t *)&(pxList->xListEnd); ptemp = ptemp->pxNext)
	{
		printf("%d\n", ptemp->xItemValue);
	}
}

数组输出的方式

for (i = 0; i < N; i++)
{
	printf("%d ", buffer[i]);
}

是不是和输出一个数组用的是同样的套路。

下边是一些关于列表和列表项的宏

//设置列表项的拥有者
#define listSET_LIST_ITEM_OWNER(pxListItem,pxOwner)		(((pxListItem)->pvOwner) = (void *)pxOwner)
//获取列表项的拥有者
#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_NEXT(pxListItem) ( ( pxListItem )->pxNext )
//获取列表中末尾列表项的地址
#define listGET_END_MARKER(pxList) ((ListItem_t const *)(&((pxList)->xListEnd)))
//判断列表是否为空
#define listLIST_IS_EMPTY( pxList )	(((pxList)->uxNumberOfItems == 0)? ( pdTRUE ) : ( pdFALSE ))
//获取列表的长度
#define listCURRENT_LIST_LENGTH( pxList ) ((pxList)->uxNumberOfItems)
//获取列表pxIndex所指向的列表项的pvOwner值(也就是TCB)
#define listGET_OWNER_OF_NEXT_ENTRY(pxTCB,pxList)								\
{																				\
	List_t * const pxConstList = (pxList);										\
	(pxConstList)->pxIndex = (pxConstList)->pxIndex->pxNext;					\
	if ((void *)(pxConstList)->pxIndex == (void *)((pxConstList)->xListEnd))	\
	{																			\
		(pxConstList)->pxIndex = (pxConstList)->pxIndex->pxNext;				\
	}																			\
	(pxTCB) = (pxConstList)->pxIndex->pvOwner;									\
}
//获取列表中第一个列表项的pvOwner值(也就是TCB)
#define listGET_OWNER_OF_HEAD_ENTRY( pxList )  ( (&( ( pxList )->xListEnd ))->pxNext->pvOwner )
//判断列表项是否在列表中
#define listIS_CONTAINED_WITHIN( pxList, pxListItem ) ( ( ( pxListItem )->pxContainer == ( pxList ) ) ? ( pdTRUE ) : ( pdFALSE ) )
//获取列表项属于哪条列表
#define listLIST_ITEM_CONTAINER( pxListItem ) ( ( pxListItem )->pxContainer )
//判断列表是否被初始化
#define listLIST_IS_INITIALISED( pxList ) ( ( pxList )->xListEnd.xItemValue == portMAX_DELAY )

你可能感兴趣的:(FreeRTOS,列表,数据结构,链表)