详解FreeRTOS开发过程(二)-- 列表和列表项

一.列表和列表项

1.列表

列表是FreeRTOS中的一个数据结构,概念上和链表有点类似,列表被用来跟踪FreeRTOS中的任务。与列表相关的全部东西都在文件list.c和list.h中。在 list.h中定义了一个叫List_t的结构体,如下:

cpp 复制代码
typedef struct xLIST
{
	listFIRST_LIST_INTEGRITY_CHECK_VALUE    //用来检查列表完整性	
	configLIST_VOLATILE UBaseType_t uxNumberOfItems;    //用来记录列表中列表项的数量
	ListItem_t * configLIST_VOLATILE pxIndex;    //用来记录当前列表项索引号,用于遍历列表	
	MiniListItem_t xListEnd;	//列表中最后一个列表项,用来表示列表结束						
	listSECOND_LIST_INTEGRITY_CHECK_VALUE	//用来检查列表完整性	
} List_t;

列表结构的示意图如下图所示:

2.列表项

列表项就是存放在列表中的项目,FreeRTOS提供了两种列表项:列表项和迷你列表项。这 两个都在文件list.h中有定义,先来看一下列表项,定义如下:

cpp 复制代码
struct xLIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE    //用来检查列表项完整性的	
	configLIST_VOLATILE TickType_t xItemValue;    //列表项值			
	struct xLIST_ITEM * configLIST_VOLATILE pxNext;    //指向下一个列表项		
	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;	//指向前一个列表项
	void * pvOwner;    //记录此链表项归谁拥有,通常是任务控制块。										
	void * configLIST_VOLATILE pvContainer;		//用来记录此列表项归哪个列表		
	listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE    //用来检查列表项完整性的			
};
typedef struct xLIST_ITEM ListItem_t;					

列表项结构的示意图如下图所示:

3.迷你列表项

迷你列表项的定义如下所示:

cpp 复制代码
struct xMINI_LIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE			
	configLIST_VOLATILE TickType_t xItemValue;
	struct xLIST_ITEM * configLIST_VOLATILE pxNext;
	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

有些情况下我 们不需要列表项这么全的功能,可能只需要其中的某几个成员变量,如果此时用列表项的话会 造成内存浪费,比如上面列表结构体 List_t 中表示最后一个列表项的成员变量 xListEnd 就是 MiniListItem_t 类型的。

二.列表和列表项初始化

1.列表初始化

新创建或者定义的列表需要对其做初始化处理,列表的初始化其实就是初始化列表结构体 List_t 中的各个成员变量,列表的初始化通过使函数vListInitialise()来完成,此函数在list.c中有定义,函数如下:

cpp 复制代码
void vListInitialise( List_t * const pxList )
{
	pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );			
	//xListEnd用来表示列表的末尾,而pxIndex表示列表项的索引号,此时列表只有一个列表项,那就是xListEnd,所以pxIndex指向xListEnd。 
	pxList->xListEnd.xItemValue = portMAX_DELAY;
    //xListEnd 的列表项值初始化为portMAX_DELAY,在文件portmacro.h 中有定义。根据所使用的MCU的不同,portMAX_DELAY值也不相同,
	pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );	
    //初始化列表项xListEnd 的 pxNext 变量,因为此时列表只有一个列表项xListEnd,因此pxNext只能指向自身。 
	pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );
    //初始化xListEnd的pxPrevious 变量,指向xListEnd自身。
	pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
    //由于此时没有其他的列表项,因此uxNumberOfItems为0
	listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
	listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}

列表初始化完以后如下图所示:

2.列表项初始化

同列表一样,列表项在使用的时候也需要初始化,列表项初始化由函数vListInitialiseItem()来完成,函数如下:

cpp 复制代码
void vListInitialiseItem( ListItem_t * const pxItem )
{
	pxItem->pvContainer = NULL;

	listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
	listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}

列表项的初始化很简单,只是将列表项成员变量pvContainer初始化为NULL,并且给用于完整性检查的变量赋值。列表项要根据实际使用情况来初始化,比如任务创建函数xTaskCreate()就会对任务堆栈中的xStateListItem和xEventListItem这两个列表项中的其他成员变量在做初始化。

3.列表项插入

列表项的插入操作通过函数vListInsert()来完成,函数原型如下:

cpp 复制代码
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
pxList:列表项要插入的列表
pxNewListItem:要插入的列表项。

函数vListInsert()的参数pxList决定了列表项要插入到哪个列表中,pxNewListItem决定了要插入的列表项,要插入的位置由列表项中成员变量 xItemValue来决定。vListInsert()的整个运行过程,函数代码如下:

cpp 复制代码
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 );
    
    //如果要插入的列表项的值等于portMAX_DELAY,要插入的位置就是列表最末尾了。
	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 )++;
}

4.列表项插入过程图示

a.插入值为40的列表项

在一个空的列表List中插入一个列表值为40的列表项ListItem1,插入完成以后如下图所示:

注意观察插入完成以后列表List 和列表项 ListItem1 中各个成员变量之间的变化,比如列表List 中的uxNumberOfItems 变为了1,表示现在列表中有一个列表项。列表项ListItem1中的 pvContainer 变成了List,表示此列表项属于列表List。通过上图可以看出,列表是一个环形的,即环形列表。

b.插入值为60的列表项

接着再插入一个值为60的列表项ListItem2,插入完成以后如下图所示:

函数vListInsert()在插入列表项是按照升序的方式插入的,所以ListItem2 肯定是插入到ListItem1的后面、xListEnd的前面。同样的,列表List的uxNumberOfItems再次加一变为2了,说明此时列表中有两个列表项。

5.列表项末尾插入

列表末尾插入列表项的操作通过函数vListInsertEnd()来完成,函数原型如下:

cpp 复制代码
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )

pxList:列表项要插入的列表
pxNewListItem:要插入的列表项

函数vListInsertEnd()源码如下:

cpp 复制代码
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
    ListItem_t * const pxIndex = pxList->pxIndex;

    //对列表和列表项的完整性检查
	listTEST_LIST_INTEGRITY( pxList );
	listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );

    //vListInsertEnd()是往列表的末尾添加列表项的,这里所谓的末尾要根据列表的成员变量
//pxIndex来确定的,前面说了列表中的pxIndex成员变量是用来遍历列表的,pxIndex所指向的
//列表项就是要遍历的开始列表项,也就是说pxIndex所指向的列表项就代表列表头,由于是个
//环形列表,所以新的列表项就应该插入到pxIndex所指向的列表项的前面。
	pxNewListItem->pxNext = pxIndex;
	pxNewListItem->pxPrevious = pxIndex->pxPrevious;

	mtCOVERAGE_TEST_DELAY();

	pxIndex->pxPrevious->pxNext = pxNewListItem;
	pxIndex->pxPrevious = pxNewListItem;
    
    //标记新的列表项pxNewListItem属于列表pxList。
	pxNewListItem->pvContainer = ( void * ) pxList;
    
    //记录列表中的列表项数目的变量加一
	( pxList->uxNumberOfItems )++;
}

6.列表项末尾插入图示

a.默认列表

在插入列表项之前我们先准备一个默认列表,如下图所示:

列表的pxIndex 所指向的列表项,这里为ListItem1,不再是xListEnd了 。

b.插入值为50的列表项

在上面的列表中插入一个值为50的列表项ListItem3,插入完成以后如下图所示:

列表List的pxIndex 指向列表项ListItem1,因此调用函数vListInsertEnd()插入ListItem3的话就会在ListItem1 的前面插入。

7.列表项的删除

列表项的删除通过函数 uxListRemove()来完成,函数原型如下:

cpp 复制代码
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
pxItemToRemove:要删除的列表项

列表项的删除只是将指定的列表项从列表中删除掉,并不会将这个列表项的内存给释放掉,如果这个列表项是动态分配内存的话。函数uxListRemove()的源码如下:

cpp 复制代码
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
    //要删除一个列表项我们得先知道这个列表项处于哪个列表中,直接读取列表项中的成员变量pvContainer
    List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;

    //将要删除的列表项的前后两个列表项"连接"在一起。 
	pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
	pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;

	mtCOVERAGE_TEST_DELAY();
    
    //pxIndex正好指向要删除的列表项,删除列表项以后pxIndex就是被删除列表项的前一个列表项。
	if( pxList->pxIndex == pxItemToRemove )
	{
		pxList->pxIndex = pxItemToRemove->pxPrevious;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

    //被删除列表项的成员变量pvContainer清零。
	pxItemToRemove->pvContainer = NULL;
	( pxList->uxNumberOfItems )--;
    
    //返回新列表的当前列表项数目。
	return pxList->uxNumberOfItems;
}

8.列表的遍历

列表List_t中的成员变量pxIndex是用来遍历列表的,FreeRTOS 提供了一个函数来完成列表的遍历,这个函数是listGET_OWNER_OF_NEXT_ENTRY()。每调用一次这个函数列表的pxIndex变量就会指向下一个列表项,并且返回这个列表项的pxOwner变量值。这个函数本质上是一个宏,这个宏在文件list.h中如下定义:

cpp 复制代码
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )										
{		
    //pxTCB 用来保存pxIndex所指向的列表项的pvOwner变量值																					
    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;											
}
相关推荐
阿捏利5 小时前
【加解密与C】Rot系列(四)Rot47
c语言·rot47
宴之敖者、7 小时前
数组——初识数据结构
c语言·开发语言·数据结构·算法
青小莫7 小时前
c语言-数据结构-二叉树OJ
c语言·开发语言·数据结构·二叉树·力扣
亿道电子Emdoor8 小时前
[ARM]MDK出现报错error: A\L3903U的解决方法
arm开发·stm32·单片机·arm
CC呢8 小时前
基于单片机智能消毒柜设计
stm32·单片机·嵌入式硬件·消毒柜
悲伤小伞9 小时前
Linux_Ext系列文件系统基本认识(一)
linux·运维·服务器·c语言·编辑器
wai歪why9 小时前
C语言:深入理解指针(1)
c语言·开发语言
橙小花10 小时前
C语言:break、continue、猜拳游戏
c语言·算法·游戏
程序员编程指南11 小时前
Qt开发环境搭建全攻略(Windows+Linux+macOS)
linux·c语言·c++·windows·qt
我爱学嵌入式11 小时前
C语言第 4 天学习笔记:位运算、流程控制与输入输出
linux·c语言·笔记