FreeRTOS-深入分析列表与列表项

FreeRTOS-深入分析列表与列表项

列表和列表项是FreeRTOS中的一个数据结构,FreeRTOS源码中大量使用了列表项,所以有必要深入学习一下列表和列表项,以便为后续学习做铺垫。

一、列表项与列表结构体定义

  • 既然列表和列表项是一个数据结构,那么我们有必要看一下它们的结构体定义,在list.h文件中我们找到了定义的结构体。
  • 先看列表项结构体定义
struct xLIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
	configLIST_VOLATILE TickType_t xItemValue;			/*< The value being listed.  In most cases this is used to sort the list in descending order. */
	struct xLIST_ITEM * configLIST_VOLATILE pxNext;		/*< Pointer to the next ListItem_t in the list. */
	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;	/*< Pointer to the previous ListItem_t in the list. */
	void * pvOwner;										/*< Pointer to the object (normally a TCB) that contains the list item.  There is therefore a two way link between the object containing the list item and the list item itself. */
	void * configLIST_VOLATILE pvContainer;				/*< Pointer to the list in which this list item is placed (if any). */
	listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
};
typedef struct xLIST_ITEM ListItem_t;	
  • listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE和listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE这两个成员是用来检查列表的完整性的,我们在这里不做讨论。
  • xItemValue 表示列表项的值,可用于后续升序插入。
  • pxNext 是指向下一个列表项,它的变量类型是struct xLIST_ITEM *即列表项,所以我们可以确定的是,在FreeRTOS中,列表类似于链表。
  • pxPrevious 表示指向前一个列表项,它的变量类型同pxNext。所以根据这里我们可以判断出,FreeRTOS中的列表很有可能是双向链表。
  • pvOwner 表示该列表所归属的任务。
  • pvContainer 表示该列表项归属的列表。

示意图如下图所示。
在这里插入图片描述


  • 因为有些情况下我们并不需要列表项这么多得功能,所以FreeRTOS中又提供了mini列表项,其结构体定义如下。
struct xMINI_LIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
	configLIST_VOLATILE TickType_t xItemValue;
	struct xLIST_ITEM * configLIST_VOLATILE pxNext;
	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;
  • mini列表项相比列表项就简化很多,它仅保留了xItemValue、pxNext、pxPrevious这三个成员。这三个成员定义与列表项相同。示意图如下图所示。
    在这里插入图片描述

  • 有了列表项之后,把列表项串起来就可以组成列表了,所以列表项是列表的组成单元。其结构体定义如下。
typedef struct xLIST
{
	listFIRST_LIST_INTEGRITY_CHECK_VALUE				/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
	configLIST_VOLATILE UBaseType_t uxNumberOfItems;
	ListItem_t * configLIST_VOLATILE pxIndex;			/*< Used to walk through the list.  Points to the last item returned by a call to listGET_OWNER_OF_NEXT_ENTRY (). */
	MiniListItem_t xListEnd;							/*< List item that contains the maximum possible item value meaning it is always at the end of the list and is therefore used as a marker. */
	listSECOND_LIST_INTEGRITY_CHECK_VALUE				/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
} List_t;

  • 其中listFIRST_LIST_INTEGRITY_CHECK_VALUE和listSECOND_LIST_INTEGRITY_CHECK_VALUE与列表项中意义相同都是检查列表完整性的,我们在这里不予讨论。
  • uxNumberOfItems 该成员记录了列表中列表项的数量。
  • pxIndex 用于表示列表中当前列表项。变量类型为ListItem_t *
  • xListEnd 表示列表中结尾列表项。变量类型为MiniListItem_t

列表示意图如下
在这里插入图片描述


  • 到这里就将列表和列表项的结构体讲述完了,接下来讲述一下列表与列表项初始化。

二、列表与列表项初始化

  • 在FreeRTOS中提供了列表和列表项初始化API,我们只需要调用这些API就能完成对列表和列表项的初始化。

1、列表项初始化

  • 列表项初始化函数如下
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 );
}
  • 从上面函数中可见,FreeRTOS的列表项初始化函数很简单,只是将所要初始化的列表项pvContainer成员即所属列表赋值为NULLlistSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem )和
    listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem )两个函数是对列表项进行完整检查的,我们这里没有实际定义相应函数,所以可以忽略。

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;
	listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
	listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}
  • 将pxList->pxIndex指向pxList->xListEnd ,即当前项指向列表结尾项。那么问题来了,pxIndex成员类型是列表项即ListItem_t*,而xListEnd成员类型是MiniListItem_t即mini列表项,那么怎么能直接将pxIndex指向xListEnd呢?其实对比二者结构体不难发现ListItem_t的xItemValue、pxNext;、pxPrevious这三个成员与MiniListItem_t中的这三个成员是一 一对应的并,并且在结构体中,我们知道结构体成员的地址是相对结构体首地址来做偏移的,那么既然这三项不但类型是一 一对应的就连定义顺序也是一 一对应的,那么这三项就完全是一 一对应的关系。这样xListEnd的地址就完全映射到了pxIndex上。
  • pxList->xListEnd.xItemValue = portMAX_DELAY,将xListEnd.xItemValue的值变为0xFFFFFFFF,这个值是根据MCU来决定的,因为stm32是32位的MCU,所以这里是取32位的最大值。
  • pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );将xListEnd.pxNext指向末尾项首地址。
  • pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );将xListEnd.pxNext指向末尾项首地址。
  • pxList->uxNumberOfItems = ( UBaseType_t ) 0U;列表中列表项数目初始化为0。
  • 最后两个是做的完整性检查,这里没有用到。

列表初始化后示意图如下。
在这里插入图片描述


  • 到这里我们已经将列表和列表项初始化讲述完了,接下来就讲述列表相关操作。

三、列表与列表项相关操作

  • 列表初始化后就是一个空表,没有一个列表项,而列表项初始化后也是孤零零的没有归属。所以FreeRTOS提供了两种方法,将列表项串到列表中,这两种方法分别是列表末尾插入和列表顺序插入。下面分别来讲述。

1、列表末尾插入

  • 列表末尾插入就是在列表的末尾插入新的列表项。下面是函数定义。
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 );
	pxNewListItem->pxNext = pxIndex;
	pxNewListItem->pxPrevious = pxIndex->pxPrevious;

	mtCOVERAGE_TEST_DELAY();
	pxIndex->pxPrevious->pxNext = pxNewListItem;
	pxIndex->pxPrevious = pxNewListItem;
	pxNewListItem->pvContainer = ( void * ) pxList;
	( pxList->uxNumberOfItems )++;
}

这里我们以先后插入值为10,20的列表项为例,来说明这段代码含义。下面是初始化完成的一个列表和两个列表项。
在这里插入图片描述

  • 首先插入列表项1。函数第一句:pxIndex = pxList->pxIndex;即pxIndex指向pxList->pxIndex所指向的单元,而初始化完毕后pxList->pxIndex指向pxList->xListEnd,即pxIndex指向了pxList->xListEnd。
  • 第二句和第三局都是检查列表完整性,这里不考虑
  • 第四句:pxNewListItem->pxNext = pxIndex;即pxNewListItem->pxNext 指向了pxIndex所指向的单元(pxList->xListEnd),这一步执行完毕后关系图如下:
    在这里插入图片描述
  • 第五句:pxNewListItem->pxPrevious = pxIndex->pxPrevious;,即pxNewListItem->pxPrevious 指向了pxEnd中pxPrevious所指向的单元即(pxEnd)。所以这一步执行完毕后关系图如下。
    在这里插入图片描述
  • 第六句:该函数在测试期间调用,这里未实际定义,所以不用考虑。
  • 第七句: pxIndex->pxPrevious->pxNext = pxNewListItem;因为pxIndex指向pxEnd,所以pxIndex->pxPrevious就相当于pxEnd->pxPrevious,而pxEnd->pxPrevious依旧指向pxEnd,所以就相当于pxEnd->pxNext=pxNewListItem,即pxEnd->pxNext指向了新列表项。关系图如下。
    在这里插入图片描述
  • 第八句:pxIndex->pxPrevious = pxNewListItem;前面已经分析过pxIndex->pxPrevious其实就是pxEnd->pxPrevious,所以这样就相当于pxEnd->pxPrevious指向pxNewListItem。关系图如下。

在这里插入图片描述

  • 第九局:pxNewListItem->pvContainer = ( void * ) pxList;确定列表项归属。

  • 第十句:( pxList->uxNumberOfItems )++;列表中列表项数目+1。到这里就已经将单个插入过程分析完了。


  • 接下来简要分析插入第二个列表项过程。这里只列举关键部分变化图。
  • pxIndex = pxList->pxIndex;
    在这里插入图片描述
  • pxNewListItem->pxNext = pxIndex;

在这里插入图片描述

  • pxNewListItem->pxPrevious = pxIndex->pxPrevious;
    在这里插入图片描述
  • pxIndex->pxPrevious->pxNext = pxNewListItem;

在这里插入图片描述

  • pxIndex->pxPrevious = pxNewListItem;
    在这里插入图片描述

  • 列表末尾插入就分析完了,剩下再插入操作同上,就不再分析了。只需要记住,这种插入方法,pxEnd->pxPrevious永远指向最后一个列表项首地址,最后一个列表项的pxNext永远指向pxEnd,剩下的依次串起来就行,这样组成的列表其实就是一个循环双向链表。


2、列表顺序插入

  • 前面分析了列表末尾插入方法,接下来分析列表顺序插入,列表顺序插入,就是按照列表项的值,对列表项降序插入。函数定义如下。
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
    ListItem_t *pxIterator;
    const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
	listTEST_LIST_INTEGRITY( pxList );
	listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
	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 )++;
}
  • 列表顺序插入首先判断了一下列表是否满了,即列表项数目是否已经达到0xffffffff,没有达到则利用for循环遍历至pxIterator->pxNext->xItemValue <= xValueOfInsertion。然后再此处插入新列表项。分析方法同上面末尾插入列表项。这里就不再赘述了。

3、列表删除

  • 既然列表有插入操作,那必定也会有删除操作,FreeRTOS同样为我们提供了删除操作的函数,函数定义如下。

UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;
	pxItmToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
	pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
	mtCOVERAGE_TEST_DELAY();
	if( pxList->pxIndex == pxItemToRemove )
	{
		pxList->pxIndex = pxItemToRemove->pxPrevious;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
	pxItemToRemove->pvContainer = NULL;
	( pxList->uxNumberOfItems )--;
	return pxList->uxNumberOfItems;
}
  • 输入参数是要删除的列表项,之后获取该列表项所属列表。下面用连接图来表示指针变化关系。
  • 以1个列表项数量为2的列表为例,其连接关系如下所示。
    在这里插入图片描述
  • 现在要将列表项1从中删除。则pxItemToRemove就是列表项1的首地址。所以pxItmToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious的执行结果如下图所示。

在这里插入图片描述

  • 执行完 pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext; 后关系图如下所示。
    在这里插入图片描述
  • 执行完pxItemToRemove->pvContainer = NULL;后就相当于列表项1从列表中脱离了。关系图如下。
    在这里插入图片描述
  • 这样就将列表项从列表中删除了。

4、列表遍历

  • 在使用列表的时候,我们有时候会用到列表遍历这个功能,但FreeRTOS并未提供相关的API,这里提供一种遍历方法,供参考,代码如下。
  List_t list;
  ListItem_t listItem1;
  ListItem_t listItem2;
  ListItem_t listItem3;
void List_Traverse(void)
{
   u8 i=0;
   ListItem_t* plistItem;
	
   vListInitialiseItem(&listItem1);
   vListInitialiseItem(&listItem2);
   vListInitialiseItem(&listItem3);
   vListInitialise(&list);
   listItem1.xItemValue = 20;
   listItem2.xItemValue = 10;
   listItem3.xItemValue = 30;
   vListInsert(&list,&listItem2);
   vListInsert(&list,&listItem3);
   vListInsert(&list,&listItem1);
	for (i=0,plistItem=list.pxIndex->pxNext; i<list.uxNumberOfItems; i++,plistItem = plistItem->pxNext)
	{
		printf("%d ",plistItem->xItemValue); 
	 }
}

到这里我们就深入分析完列表与列表项了,列表与列表项是学习FreeRTOS的基础,在FreeRTOS中有大量应用,所以这一部分必须得理解深入。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值