STM32F103移植FreeRTOS系列十一:列表和列表项(原理详解)

目录

11.1什么是列表项和列表项

11.1.1列表

 11.1.2列表

11.1.3 迷你列表项

11.2列表相关API函数介绍

11.2.1 初始化列表函数 vListInitialise()

11.2.2 初始化列表项 vListInitialiseItem()

11.2.3 列表插入列表项 vListInsert()

11.2.4 列表末尾插入列表项 vListInsertEnd()

11.2.5 列表移除列表项 uxListRemove()


11.1什么是列表项和列表项

11.1.1列表

第七章 FreeRTOS 列表和列表项

 11.1.2列表项

与列表相关的全部东西都在文件 list.c 和 list.h 中。在 list.h 中定义了一个叫 List_t 的 结构体,如下:

typedef struct xLIST
{
    	listFIRST_LIST_INTEGRITY_CHECK_VALUE			/* 校验值 */
    	volatile UBaseType_t uxNumberOfItems;			/* 列表中的列表项数量 */
   	    ListItem_t * configLIST_VOLATILE pxIndex		/* 用于遍历列表项的指针 */
    	MiniListItem_t xListEnd					        /* 末尾列表项 */
    	listSECOND_LIST_INTEGRITY_CHECK_VALUE		    /* 校验值 */
} List_t;

 (1) 和 (5) 、 这 两 个 都 是 用 来 检 查 列 表 完 整 性 的 , 需 要 将 宏 configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 设置为 1,开启以后会向这两个地方分别 添加一个变量 xListIntegrityValue1 xListIntegrityValue2,在初始化列表的时候会这两个变量中 写入一个特殊的值。FreeRTOS通过检查这两个常量的值,来判断列表的数据在程序运行过程中,是否遭到破坏 ,该功能一般用于调试, 默认是不开启的 。以后我们在学习列表的时候不讨论这个功能!

(2)、uxNumberOfItems 用来记录列表中列表项的数量(不包含 xListEnd)

(3)、pxIndex 用来记录当前列表项索引号,用于遍历列表。

(4)、 xListEnd 列表中最后一个列表项,用来表示列表结束,此变量类型为 MiniListItem_t,这是一个 迷你列表项,关于列表项稍后讲解。

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

 注意!图中并未列出用于列表完整性检查的成员变量。

11.1.3 迷你列表项

迷你列表项在文件 list.h 中有定义

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;

1、成员变量 xItemValue 为列表项的值,这个值多用于按升序对列表中的列表项进行排序

2、成员变量 pxNext 和 pxPrevious 分别用于指向列表中列表项的下一个列表项和上一个列表项

3、迷你列表项只用于标记列表的末尾和挂载其他即将插入列表中的列表项,因此不需要成员变量 pxOwner 和 pxContainer,以节省内存开销

4、只需要其中的某几个成员变量的话,使用迷你列表项的话不会造成内存浪费。

11.2列表相关API函数介绍

11.2.1 初始化列表函数 vListInitialise()

此函数在 list.c 中有 定义,函数如下:

void vListInitialise( List_t * const pxList )
{
    /*  初始化时,列表中只有 xListEnd,因此 pxIndex 指向 xListEnd */
    pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd ); //(1)

    /* xListEnd 的值初始化为最大值,用于列表项升序排序时,排在最后 */
    pxList->xListEnd.xItemValue = portMAX_DELAY; //(2)

    /* 初始化时,列表中只有 xListEnd,因此上一个和下一个列表项都为 xListEnd 本身 */
    pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd ); //(3)
    pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd ); //(4)

    /* 初始化时,列表中的列表项数量为 0(不包含 xListEnd) */
    pxList->uxNumberOfItems = ( UBaseType_t ) 0U; //(5)

    /* 初始化用于检测列表数据完整性的校验值 */
    listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList ); //(6)
    listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList ); //(7)
}

(1)、xListEnd 用来表示列表的末尾,而 pxIndex 表示列表项的索引号,此时列表只有一个 列表项,那就是 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。

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

                                  

11.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 );
}

列表项的初始化很简单,只是将列表项成员变量 pvContainer 初始化为 NULL。列表项要根据实际使用情况来初始化

11.2.3 列表插入列表项 vListInsert()

 列表项的插入操作通过函数 vListInsert()来完成

void vListInsert( List_t * const pxList, 
ListItem_t * const pxNewListItem )

形参

描述

pxList

列表

pxNewListItem

待插入列表项

此函数用于将待插入列表的列表项按照列表项值升序进行排序,有序地插入到列表中 。列表项具体插入到的位置由列表项中成员变量 xItemValue 来决定。而且插入的列表项根据 xItemValue 的值按照升序的方式排列

这里的话我简单贴一下开发手册的源码吧,以后复习懒得翻阅手册了,不想看的直接跳就好

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 ); 				

     /*3 如果待插入列表项的值为最大值 */ 
	if( xValueOfInsertion == portMAX_DELAY )
 	{ 
		pxIterator = pxList->xListEnd.pxPrevious; 				/*4 插入的位置为列表 xListEnd 前面 */ 
	} else 
	{
		for(  pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); 			/*5遍历列表中的列表项,找到插入的位置*/ 
		         pxIterator->pxNext->xItemValue <= xValueOfInsertion; 
		         pxIterator = pxIterator->pxNext  ) { }
	} 
	pxNewListItem->pxNext = pxIterator->pxNext;					/*6 将待插入的列表项插入指定位置 */ 
 	pxNewListItem->pxNext->pxPrevious = pxNewListItem; 
	pxNewListItem->pxPrevious = pxIterator; 
	pxIterator->pxNext = pxNewListItem; 
	pxNewListItem->pxContainer = pxList; 						/* 7更新待插入列表项所在列表 */ 
	( pxList->uxNumberOfItems )++;							/* 8更新列表中列表项的数量 */ 

(1)、获取要插入的列表项值,即列表项成员变量 xItemValue 的值,因为要根据这个值来确 定列表项要插入的位置。

(2)、这一行和下一行代码用来检查列表和列表项的完整性的。其实就是检查列表和列表项中用于完整性检查的变量值是否被改变。这些变量的值在列表和列表项初始化的时候就被写入 了,这两行代码需要实现函数 configASSERT()!

(3)、要插入列表项,第一步就是要获取该列表项要插入到什么位置!如果要插入的列表项的值等于 portMAX_DELAY,也就是说列表项值为最大值,if语句为真,要插入的位置就是列表最末尾了。

(4)、获取要插入点,注意!列表中的 xListEnd 用来表示列表末尾,在初始化列表的时候 xListEnd的列表值也是portMAX_DELAY,此时要插入的列表项的列表值也是portMAX_DELAY。 这两个的顺序该怎么放啊?通过这行代码可以看出要插入的列表项会被放到 xListEnd 前面pxList->xListEnd.pxPrevious就是列表最末尾的挂载点

(5)、要插入的列表项的值如果不等于 portMAX_DELAY 那么就需要在列表中一个一个的找 自己的位置,这个 for 循环就是找位置的过程,当找到合适列表项的位置的时候就会跳出。由 于这个 for 循环是用来寻找列表项插入点的,所以 for 循环体里面没有任何东西。这个查找过程是按照升序的方式查找列表项插入点的 。退出循环时pxIterator为pxNewListItem前一个的节点。

(6)、经过上面的查找,我们已经找到列表项的插入点了,从本行开始接下来的四行代码就 是将列表项插入到列表中,插入过程和数据结构中双向链表的插入类似。这里其实就是实现两两拉手的过程

7)、列表项已经插入到列表中了,那么列表项的成员变量 pvContainer 也该记录此列表项属于哪个列表的了。 

8)、列表的成员变量 uxNumberOfItems 加一,表示又添加了一个列表项

11.2.4 列表末尾插入列表项 vListInsertEnd()

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

此函数用于将待插入列表的列表项插入到列表 pxIndex 指针指向的列表项前面,是一种无序的插入方法

void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )

       形参

描述

       pxList

列表

pxNewListItem

待插入列表项

代码如下

void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
    /* 获取列表 pxIndex 指向的列表项 */
    ListItem_t * const pxIndex = pxList->pxIndex; (0)

    
    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)
}

(0) pxIndex 表示列表项的索引号,前面说了列表中的 pxIndex 成员变量是用来遍历列表的, pxIndex 所指向的列表项就是要遍历的开始列表项,也就是说 pxIndex 所指向的列表项就代表列表头!由于是个环形列表,所以新的列表项就应该插入到 pxIndex 所指向的列表项的前面

(1)、与下面的一行代码完成对列表和列表项的完整性检查。

(2)、从本行开始到(3)之间的代码就是将要插入的列表项插入到列表末尾。使用函数 vListInsert()向列表中插入一个列表项的时候这个列表项的位置是通过列表项的值,也就是列表项成员变量 xItemValue 来确定。vListInsertEnd()是往列表的末尾添加列表项的,我们知道列表中的 xListEnd 成员变量表示列表末尾的,那么函数 vListInsertEnd()插入一个列表项是不是就是 插到 xListEnd 的前面或后面啊?这个是不一定的,这里所谓的末尾要根据列表的成员变量 pxIndex 来确定的!前面说了列表中的 pxIndex 成员变量是用来遍历列表的,pxIndex 所指向的 列表项就是要遍历的开始列表项,也就是说 pxIndex 所指向的列表项就代表列表头!由于是个 环形列表,所以新的列表项就应该插入到 pxIndex 所指向的列表项的前面。

(3)、标记新的列表项 pxNewListItem 属于列表 pxList。

(4)、记录列表中的列表项数目的变量加一,更新列表项数目。

此函数用于将待插入列表的列表项插入到列表 pxIndex 指针指向的列表项前面,是一种无序的插入方法

11.2.5 列表移除列表项 uxListRemove()

UBaseType_t  uxListRemove (   ListItem_t *  const    pxItemToRemove )

此函数用于将列表项从列表项所在列表中移除

形参

描述

pxItemToRemove

待移除的列表项

返回值

描述

整数

待移除列表项移除后,所在列表剩余列表项的数量

代码详解

UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
    List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer; (1)

    //从列表中移除列表项
    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious; (2)
    pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
    
    /*如果 pxIndex 正指向待移除的列表项 */ 
    mtCOVERAGE_TEST_DELAY();
    if( pxList->pxIndex == pxItemToRemove )
    {
        pxList->pxIndex = pxItemToRemove->pxPrevious; (3)
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
     /*将待移除的列表项的所在列表指针清空*/ 
    pxItemToRemove->pvContainer = NULL; (4)

    /*更新列表中列表项的数量*/ 
    ( pxList->uxNumberOfItems )--;

    /*返回移除后的列表中列表项的数量*/ 
    rturn pxList->uxNumberOfItems; (5)
}

(1)、要删除一个列表项我们得先知道这个列表项处于哪个列表中,直接读取列表项中的成员变量 pvContainer 就可以得到此列表项处于哪个列表中

(2)、与下面一行完成列表项的删除,其实就是将要删除的列表项的前后两个列表项“连接” 在一起。

(3)、如果列表的 pxIndex 正好指向要删除的列表项,那么在删除列表项以后要重新给 pxIndex 找个“对象”啊,这个新的对象就是被删除的列表项的前一个列表项。

(4)、被删除列表项的成员变量 pvContainer 清零。将待移除的列表项的所在列表指针清空

(5)、返回新列表的当前列表项数目。

猜你喜欢

转载自blog.csdn.net/qq_51519091/article/details/131583119