FreeRTOS系列第4篇---列表和列表项

前言

列表和列表项是FreeRTOS的一个数据结构,FreeRTOS大量使用了列表和列表项。它是FreeRTOS的基石。

1、基本概念

1.1 列表

列表是 FreeRTOS 中的一个数据结构,概念上和链表有点类似,列表被用来跟踪 FreeRTOS中的任务。在 list.h 中定义了一个叫 List_t 的结构体,如下

typedef struct xLIST
{
listFIRST_LIST_INTEGRITY_CHECK_VALUE (1)
configLIST_VOLATILE UBaseType_t uxNumberOfItems; (2)
ListItem_t * configLIST_VOLATILE pxIndex; (3)
MiniListItem_t xListEnd; (4)
listSECOND_LIST_INTEGRITY_CHECK_VALUE (5)
} List_t;

(1) 和 (5) 、 这 两 个 都 是 用 来 检 查 列 表 完 整 性 的 。需要configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 设置为 1,开启以后会向这两个地方分别添加一个变量 xListIntegrityValue1 和 xListIntegrityValue2,在初始化列表的时候会这两个变量中写入一个特殊的值,默认不开启这个功能。
(2)、uxNumberOfItems 用来记录列表中列表项的数量。
(3)、pxIndex 用来记录当前列表项。
(4)、列表中最后一个列表项,用来表示列表结束,此变量类型为 MiniListItem_t。

1.2 列表项

1.2.1 列表项

列表项就是存放在列表中的项目,FreeRTOS 提供了两种列表项:列表项和迷你列表项。

struct xLIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE (1)
	configLIST_VOLATILE TickType_t xItemValue; (2)
	struct xLIST_ITEM * configLIST_VOLATILE pxNext; (3)
	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; (4)
	void * pvOwner; (5)
	void * configLIST_VOLATILE pvContainer; (6)
	listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE (7)
};
typedef struct xLIST_ITEM ListItem_t;

(1)和(7)、用法和列表一样,用来检查列表项完整性的。
(2)、xItemValue 为列表项值。
(3)、pxNext 指向下一个列表项。
(4)、pxPrevious 指向前一个列表项,和 pxNext 配合起来实现类似双向链表的功能。
(5)、pvOwner 记录此链表项归谁拥有,通常是任务控制块。
(6)、pvContainer 用来记录此列表项归哪个列表。TCB_t 中有两个变量 xStateListItem 和 xEventListItem,这两个变量
的类型就是 ListItem_t,也就是说这两个成员变量都是列表项。以 xStateListItem 为例,当创建一个任务以后xStateListItem 的 pvOwner 变量就指向这个任务的任务控制块,表示 xSateListItem属于此任务。当任务就绪态以后 xStateListItem 的变量 pvContainer 就指向就绪列表,表明此列表项在就绪列表中。

1.2.2 迷你列表项

迷你列表项在文件 list.h 中有定义,如下:

struct xMINI_LIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE (1)
	configLIST_VOLATILE TickType_t xItemValue; (2)
	struct xLIST_ITEM * configLIST_VOLATILE pxNext; (3)
	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; (4)
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

(1)、用于检查迷你列表项的完整性。
(2)、xItemValue 记录列表列表项值。
(3)、pxNext 指向下一个列表项。
(4)、pxPrevious 指向上一个列表项。
迷你列表项存在的意义:
因为有些情况下我们不需要列表项这么全的功能,可能只需要其中的某几个成员变量,如果此时用列表项的话会
造成内存浪费!比如上面列表结构体 List_t 中表示最后一个列表项的成员变量 xListEnd 就是
MiniListItem_t 类型的。

2、操作

2.1 列表初始化

列表的初始化其实就是初始化列表结构体List_t 中的各个成员变量,列表的初始化通过使函数 vListInitialise()来完成,此函数在 list.c 中有定义,函数如下:

void vListInitialise( List_t * const pxList )
{
	pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd ); (1)
	pxList->xListEnd.xItemValue = portMAX_DELAY; (2)
	pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd ); (3)
	pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd ); (4)
	pxList->uxNumberOfItems = ( UBaseType_t ) 0U; (5)
	listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList ); (6)
	listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList ); (7)
}

(1)、xListEnd 用来表示列表的末尾,此时列表只有一个列表项,那就是 xListEnd,所以 pxIndex 指向 xListEnd。、
(2)、xListEnd 的列表项值初始化为 portMAX_DELAY, portMAX_DELAY 是个宏,在文件portmacro.h 中有定义。根据所使用的 MCU 的不同,portMAX_DELAY 值也不相同,可以为 0xffff或者 0xffffffffUL,本教程中为 0xffffffffUL。
(3)、初始化列表项 xListEnd 的 pxNext 变量,因为此时列表只有一个列表项 xListEnd,因此 pxNext 只能指向自身。
(4)、同(3)一样,初始化 xListEnd 的 pxPrevious 变量,指向 xListEnd 自身。
(5)、由于此时没有其他的列表项,因此 uxNumberOfItems 为 0,注意,这里没有算 xListEnd。
(6) 和 (7) 、 初 始 化 列 表 项 中 用 于 完 整 性 检 查 字 段 ,configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 为 1 的时候才有效。同样的根据所选的MCU 不同其写入的值也不同,可以为 0x5a5a 或者 0x5a5a5a5aUL。STM32 是 32 位系统写入的是 0x5a5a5a5aUL。

2.2 列表项初始化

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

void vListInitialiseItem( ListItem_t * const pxItem )
{
	pxItem->pvContainer = NULL; //初始化 pvContainer 为 NULL
	//初始化用于完整性检查的变量,如果开启了这个功能的话。
	listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
	listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}

2.3 列表项插入

函数原型如下:

void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
	ListItem_t *pxIterator;
	const TickType_t xValueOfInsertion = pxNewListItem->xItemValue; (1)
	listTEST_LIST_INTEGRITY( pxList ); (2)
	listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
	if( xValueOfInsertion == portMAX_DELAY ) (3) 
	{
		pxIterator = pxList->xListEnd.pxPrevious; (4) 
	}
	else
	{	
		for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue<=xValueOfInsertion; pxIterator = pxIterator->pxNext )5{
			//空循环,什么也不做!
		} 
	}
	pxNewListItem->pxNext = pxIterator->pxNext; (6)
	pxNewListItem->pxNext->pxPrevious = pxNewListItem;
	pxNewListItem->pxPrevious = pxIterator;
	pxIterator->pxNext = pxNewListItem;
	pxNewListItem->pvContainer = ( void * ) pxList; (7)
	pxList->uxNumberOfItems )++; (8) 
}

(1)、获取要插入的列表项值,即列表项成员变量 xItemValue 的值,因为要根据这个值来确
定列表项要插入的位置。
(2)、这一行和下一行代码用来检查列表和列表项的完整性的。其实就是检查列表和列表项中用于完整性检查的变量值是否被改变。这些变量的值在列表和列表项初始化的时候就被写入了,这两行代码需要实现函数 configASSERT()!
(3)、要插入列表项,第一步就是要获取该列表项要插入到什么位置!如果要插入的列表项的值等于 portMAX_DELAY,也就是说列表项值为最大值,这种情况最好办了,要插入的位置就是列表最末尾了。
(4)、要插入的列表项的列表值也是portMAX_DELAY。这两个的顺序该怎么放啊?通过这行代码可以看出要插入的列表项会被放到 xListEnd 前面。
(5)、要插入的列表项的值如果不等于 portMAX_DELAY 那么就需要在列表中一个一个的找自己的位置,这个 for 循环就是找位置的过程,当找到合适列表项的位置的时候就会跳出。由于这个 for 循环是用来寻找列表项插入点的,所以 for 循环体里面没有任何东西。这个查找过程是按照升序的方式查找列表项插入点的。
为了方便理解,将插入过程用图示方式展示:

  • 插入第一个节点为普通节点时的情况

插入第一个节点为普通节点时的情况

  • 再次插入一个节点。

体现升序的插入

  • 插入节点值为portMAX_DELAY时的情况

插入节点值为portMAX_DELAY时的情况

2.4 列表项末尾插入

函数 vListInsertEnd()源码如下:

void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
	ListItem_t * const pxIndex = pxList->pxIndex;
	listTEST_LIST_INTEGRITY( pxList ); (1)
	listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
	pxNewListItem->pxNext = pxIndex; (2)
	pxNewListItem->pxPrevious = pxIndex->pxPrevious;
	mtCOVERAGE_TEST_DELAY();
	pxIndex->pxPrevious->pxNext = pxNewListItem;
	pxIndex->pxPrevious = pxNewListItem;
	pxNewListItem->pvContainer = ( void * ) pxList; (3)
	pxList->uxNumberOfItems )++; (4)
}

(1)、与下面的一行代码完成对列表和列表项的完整性检查。
(2)、从本行开始到(3)之间的代码就是将要插入的列表项插入到列表末尾。使用函数vListInsert()向列表中插入一个列表项的时候这个列表项的位置是通过列表项的值,也就是列表项成员变量 xItemValue 来确定。vListInsertEnd()是往列表的末尾添加列表项的,我们知道列表中的 xListEnd 成员变量表示列表末尾的,那么函数 vListInsertEnd()插入一个列表项是不是就是插到 xListEnd 的前面或后面啊?这个是不一定的,这里所谓的末尾要根据列表的成员变量pxIndex 来确定的!前面说了列表中的 pxIndex 成员变量是用来遍历列表的,pxIndex 所指向的列表项就是要遍历的开始列表项,也就是说 pxIndex 所指向的列表项就代表列表头!由于是个环形列表,所以新的列表项就应该插入到 pxIndex 所指向的列表项的前面。
(3)、标记新的列表项 pxNewListItem 属于列表 pxList。
(4)、记录列表中的列表项数目的变量加一,更新列表项数目。
插入图示:
在插入列表项之前我们先准备一个默认列表,如图 7.4.2.1 所示
在这里插入图片描述
插入值为50的列表项:
在这里插入图片描述
列表 List 的 pxIndex 指向列表项 ListItem1,因此调用函数 vListInsertEnd()插入 ListItem3 的话就会在 ListItem1 的前面插入。

2.5 列表项的删除

函数原型如下:

/*
	入口参数:要删除的项
	const指针的定义:
  const指针是指针变量的值一经初始化,就不可以改变指向,初始化是必要的。其定义形式如下:
	type *const 指针名称;
  声明指针时,可以在类型前或后使用关键字const,也可在两个位置都使用。例如,下面都是合法的声明,但是含义大不同:
	const int * pOne;    //指向 整形常量 的指针,它指向的值不能修改
	int * const pTwo;    //指向整形的常量指针 ,它不能在指向别的变量,但指向(变量)的值可以修改。 
	const int *const pThree;  //指向整形常量 的常量指针 。它既不能再指向别的常量,指向的值也不能修改。
	返回值:
	返回删除列表项以后的列表剩余列表项数目。注意,列表项的删除只是将指定的列表项从列表中删除掉,并不会将这个列表项的内存给
	释放掉!如果这个列表项是动态分配内存的话。
*/
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove ) 

2.6 列表项的遍历

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

#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \ (1)
{ \
	List_t * const pxConstList = ( pxList ); \
	( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \ (2)
	if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )\ (3)
	{ \
		( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \ (4)
	} \
	( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \ (5)
}

(1)、pxTCB 用来保存 pxIndex 所指向的列表项的 pvOwner 变量值,也就是这个列表项属于
谁的?通常是一个任务的任务控制块。pxList 表示要遍历的列表。
(2)、列表的 pxIndex 变量指向下一个列表项。
(3)、如果 pxIndex 指向了列表的 xListEnd 成员变量,表示到了列表末尾。
(4)、如果到了列表末尾的话就跳过 xListEnd,pxIndex 再一次重新指向处于列表头的列表
项,这样就完成了一次对列表的遍历。
(5)、将 pxIndex 所指向的新列表项的 pvOwner 赋值给 pxTCB。
此函数用于从多个同优先级的就绪任务中查找下一个要运行的任务。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值