目录
列表和列表项是 FreeRTOS 的一个数据结构,FreeRTOS 大量使用到了列表和列表项,它是 FreeRTOS 的基石。
1. 什么是列表和列表项?
1.1 列表
列表是 FreeRTOS 中的一个数据结构,概念上和链表有些类似,列表被用来跟踪 FreeRTOS 中的任务。与列表相关的全部东西都在文件 list.c 和 list.h 中。在 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_BYE 设置为1
//开启以后会向这两个地方分别添加一个变量 xListIntegrityValue1
//和 xListIntegrityValue2,在初始化列表的时候会在这两个变量中
//写入一个特殊的值,默认不开启这个功能。
//(2)中 uxNumberOfItems 用来记录列表中列表项的数量。
//(3)中 pxIndex 用来记录当前列表项索引号,用于遍历列表。
//(4)中 xListEnd 列表中最后一个列表项,用来表示列表结束,
//此变量的类型为 MiniListItem_t,这是一个迷你列表项。
列表结构示意图如下所示:
注意!上图并没有列出用于列表完整性检查的成员变量。(也就是上述结构体中标注的 (1) 和 (5),用来检查列表完整性的成员变量 )
1.2 列表项
列表项就是存放在列表中的项目,FreeRTOS 提供了两种列表项:列表项和迷你列表项。这两个都在文件 list.h 中有定义;
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 用来记录此列表项归哪个列表。注意和 pvOwner 的区别,在前面讲解任务控制块 TCB_t的
//时候说了在 TCB_t 中有两个变量 xStateListItem 和 xEventListItem,这两个变量的类型
//就是 ListItem_t,也就是说这两个成员变量都是列表项。
//以 xStateListItem 为例,当创建一个任务以后 xStateListItem 的 pvOwner 变量就指向这个任务
//的任务控制块,表示 xStateListItem 属于此任务。当任务就绪态以后 xStateListItem 的变量
//pvContainer 就指向就绪列表,表明此列表在就绪列表中。
//举个比较通俗的例子:小王在上二年级,他的父亲是老王。如果把小王比作列表项,那么小王的 pvOwner
//属性值就是老王(属于任务控制块,他是老王的儿子,他始终都属于老王);小王的 pvContainer
//属性值就是二年级(列表归谁所有,目前小王上二年级,所以它属于二年级,当小王上三年级以后
//pvContainer 的属性值就变成三年级了,意思大概就是这样!!!)
列表项的结构示意图如下:
注意!上图并没有列出用于列表完整性检查的成员变量。(也就是上述结构体中标注的 (1) 和 (7),用来检查列表完整性的成员变量 )
1.3 迷你列表项
迷你列表项在文件 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 指向上一个列表项
//之所以使用迷你列表项,是因为有些情况下我们不需要列表项这么全的功能,
//可能只需要其中的某几个成员变量,如果此时用列表项的话会造成内存浪费!
迷你列表项结构示意图如下:
注意!上图并没有列出用于列表完整性检查的成员变量。(也就是上述结构体中标注的 (1),用来检查列表完整性的成员变量 )
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 用来表示列表的末尾,而 pxIndex 表示列表项的索引号,初始化时列表中只有一个列表项,那就是 xListEnd(类似于链表中默认指针指向头结点),所以 pxIndex 指向 xListEnd。
(2)、xListEnd 的列表项值初始化为 portMAX_DELAY,portMAX_DELAY 是个宏,在文件 portmacro.h 中有定义。根据所使用的 MCU 的不同,portMAX_DELAY 值也不相同,可以为 0xffff 或者 0xffffffffUL,本次使用的是 0xffffffffUL。
(3)、初始化列表项 xListEnd 的 pxNext 变量,因为此时列表只有一个列表项 xListEnd ,因此 pxNext 只能指向自身。
(4)、初始化列表项 xListEnd 的 pxPrevious 变量,因为此时列表只有一个列表项 xListEnd ,因此 pxPrevious 只能指向自身。
(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);
}
注:
列表项的初始化很简单,只是将列表项成员变量 pvContainer 初始化为 NULL,并且给用于完整性检查的变量赋值。这里需要注意,虽然列表项的成员比列表要多,但是在初始化的时候只是初始化了 pvContainer;这是因为列表项要根据实际的使用情况来初始化,比如说创建任务函数 xTaskCreate() 就会对任务堆栈中的 xStateListItem 和 xEventListItem 这两个列表项中的其他成员变量再做初始化;
3. 列表项插入
3.1 列表项插入函数分析
列表项的插入操作通过函数 vListInsert() 来完成:
void vListInsert(List_t* const pxList,
ListItem_t* const pxNewListItem)
参数:
pxList: 列表项要插入的列表
pxNewListItem: 要插入的列表项
返回值:
无
函数 vListInsert() 的参数 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 );
if( xValueOfInsertion == portMAX_DELAY ) (3)
{
pxIterator = pxList->xListEnd.pxPrevious; (4)
}
else
{
for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->\ (5)
pxNext->xItemValue <=xValueOfInsertion; pxIterator = pxIterator->pxNext )
{
//空循环,什么也不做!
}
}
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)、获取要插入点,注意!列表中的 xListEnd 用来表示列表末尾,在初始化列表的时候 xListEnd 的列表值也是 portMAX_DELAY,此时要插入的列表项的列表值也是 portMAX_DELAY。通过这行代码可以看出要插入的列表项会被放到 xListEnd 前面。
(5)、要插入的列表项的值如果不等于 portMAX_DELAY ,那么就需要在列表中一个一个的找自己的位置,这个 for 循环就是找位置的过程,当找到合适列表项的位置的时候就会跳出。由于这个 for 循环是用来寻找列表项插入点的,所以 for 循环体里面没有任何东西。这个查找过程是按照升序的方式查找列表项插入点的。
(6)、经过上面的查找,我们已经找到列表项的插入点了,所以从本行开始的接下来四行代码就是将列表项插入到列表中,插入的过程和数据结构中的双向链表的插入类似。(首先在链表中找到插入位置,双向链表的插入先考虑后继指针的处理,最后再考虑前驱指针的处理)
(7)、列表项已经插入到列表中了,那么列表项的成员变量 pvContainer 也该记录此列表项属于哪个列表了。
(8)、列表的成员变量 uxNumberOfItems 加一,表示又添加了一个列表项。
3.2 列表项插入过程图示
本节我们会向一个空的列表中插入三个列表项,这三个列表项的值分别为 40 , 60 和 50。
3.2.1 插入值为 40 的列表项
在一个空的列表 List 中插入一个列表值为 40 的列表项 ListItem1 ,插入完成以后如下图所示:
注意观察插入完成以后的列表 List 和列表项 ListItem1 中各个成员变量之间的变化,比如说列表 List 中的 uxNumberOfItems 变为了 1 (uxNumberOfItems 记录列表中列表项的个数),表示现在列表中有一个列表项。列表项 ListItem1 中的 pvContainer 变成了 List,表示此列表属于列表 List。通过上图可以发现,列表是一个环形的,即环形列表!
3.2.2 插入值为 60 的列表项
在上面的基础上,紧接着再插入一个值为 60 的列表项 ListItem2 ,插入完成以后如下图所示:
在讲解函数 vListInsert() 的时候说过了列表项是按照升序的方式插入的,所以 ListItem2 肯定是插入到 ListItem1 的后面、xListEnd 的前面。同样的,列表 List 的 uxNumberOfItems 再次加一变为 2 了,说明此时列表中有两个列表项。这里需要注意,列表是环形的,类似于双向列表,通过前驱指针和后继指针将整个列表组成一个环形。
3.2.3 插入值为 50 的列表项
在上面的基础上,再插入一个值为 50 的列表项 ListItem3 ,插入完成以后如下图所示:
按照升序排列的方式,ListItem3 应该放到 ListItem1 和 ListItem2 中间。列表最终形成一个环形。
4. 列表项末尾插入
4.1 列表项末尾插入函数分析
列表末尾插入列表项的操作通过函数 vListInsertEnd() 来完成,函数原型如下:
void vListInsertEnd(List_t *const pxList,
ListItem_t *const pxNewListItem)
参数:
pxList: 列表项要插入的列表
pxNewListItem: 要插入的列表项
返回值:
无
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 所指向的列表项的前面。
//这四句代码就类似于双向列表中的插入操作
//首先要明确,列表是一个环形的
pxNewListItem->pxNext = pxIndex; //因为要插入是在列表的末尾插入,pxIndex类似于索引指针
//pxIndex用于遍历整个列表,所以pxIndex指向头指针,所以这句代码的意思是要出入的列表项的后继指针指向头结点
pxNewListItem->pxPrevious = pxIndex->pxPrevious;
//还没有在列表末尾插入列表项时,头结点的前驱指针指向尾结点
//在末尾插入尾列表后,头指针的前驱指针要指向新插入的这个末尾列表项
//所以该代码的意思就是新插入列表项的前驱指针指向原本头结点指向的前驱指针
//通俗的说就是最后一个结点的前驱指针指向倒数第二个结点
//通过上述两句代码就能实现新插入结点的前驱和后继指针的指向
pxIndex->pxPrevious->pxNext = pxNewListItem;
//该代码的意思是头结点的前驱指针的后继指针指向新插入的结点
//也就是倒数第二个结点的后继指针指向倒数第一个结点
pxIndex->pxPrevious = pxNewListItem;
//头结点的前驱指针指向新插入的结点,使得整个列表形成一个环形
(3)、标记新的列表项 pxNewListItem 属于列表 pxList。
(4)、记录列表中的列表项数目的变量加一,更新列表项数目。
4.2 列表项末尾插入图示
跟函数 vListInsert() 一样,vListInsertEnd() 的插入过程如下图所示:
4.2.1 默认列表
在插入列表项之前我们先准备一个默认列表,如下图所示:
注意:列表中的 pxIndex 所指向的列表项,这里为 ListItem1 ,不再是 xListEnd 了。
4.2.2 插入值为 50 的列表项
在上面的列表中插入一个值为 50 列表项 ListItem3 ,插入完成以后如图所示:
列表 List 的 pxIndex 指向列表项 ListItem1 ,因此调用函数 xListInsertEnd() 插入 ListItem3 的话就会在 ListItem1 的前面插入。(因为pxIndex指向头结点,所以 ListItem1 对应的就是头列表,整个列表是环形的,所以在尾部插入就是在头结点的前面插入)
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;
mtCOVERAGE_TEST_DELAY();
if( pxList->pxIndex == pxItemToRemove )
{
pxList->pxIndex = pxItemToRemove->pxPrevious; (3)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
pxItemToRemove->pvContainer = NULL; (4)
( pxList->uxNumberOfItems )--;
return pxList->uxNumberOfItems; (5)
}
(1)、要删除一个列表项必须得先要知道这个列表项处于哪个列表中,直接读取列表项中的成员变量 pvContainer 就可以得到此列表项处于哪个列表中。
(2)、与下面一行代码一起完成对列表项的删除,其实就是将要删除的列表项的前后两个列表项 “连接” 在一起。(通俗点说就是:删除结点的前一个结点的后继指针指向删除的后一个结点,删除结点的后一个结点的前驱指针指向删除结点的前一个结点)
(3)、如果列表的 pxIndex 正好指向要删除的列表项,那么在删除列表项以后要重新给 pxIndex 找个 “对象” 啊,这个新的对象就是被删除的列表项的前一个列表项。
(4)、被删除的列表项的成员变量 pvContainer 清零。
(5)、返回新列表的当前列表项数目。
6. 列表的删除
列表 List_t 中的成员变量 pxIndex 是用来遍历列表的,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)、进入 if 判断语句,如果到了列表末尾的话就跳过 xListEnd,pxIndex 再一次重新指向处于列表头的列表项,这样就完成了一次对列表的遍历。
(5)、将 pxIndex 所指向的新列表项的 pvOwner 赋值给 pxTCB。
此函数用于从多个优先级的就绪任务中查找下一个要运行的任务。
7. 列表项的插入和删除实验
本实验设计 3 个任务:start_task、task1_task 和 list_task,这三个任务的任务功能如下:
start_task:用来创建其他 2 个任务。
task1_task:应用任务 1 ,控制 LED0 闪烁,用来提示系统正在运行。
list_task:列表和列表项操作任务,调用列表和列表项相关 API 函数,并且通过串口输出相应的信息来观察这些 API 函数的运行过程。
7.1 程序运行结果分析
第一步和第二步是用来初始化列表和列表项的,并且通过串口输出列表和列表项的地址,这一步是开发板复位后默认运行的:
//第一步:初始化列表和列表项
vListInitialise(&TestList);
vListInitialiseItem(&ListItem1);
vListInitialiseItem(&ListItem2);
vListInitialiseItem(&ListItem3);
ListItem1.xItemValue=40; //ListItem1列表项值为40
ListItem2.xItemValue=60; //ListItem2列表项值为60
ListItem3.xItemValue=50; //ListItem3列表项值为50
第二步打印列表和其他列表项的地址:
//第二步:打印列表和其他列表项的地址
printf("/*******************列表和列表项地址*******************/\r\n");
printf("项目 地址 \r\n");
printf("TestList %#x \r\n",(int)&TestList);
printf("TestList->pxIndex %#x \r\n",(int)TestList.pxIndex);
printf("TestList->xListEnd %#x \r\n",(int)(&TestList.xListEnd));
printf("ListItem1 %#x \r\n",(int)&ListItem1);
printf("ListItem2 %#x \r\n",(int)&ListItem2);
printf("ListItem3 %#x \r\n",(int)&ListItem3);
printf("/************************结束**************************/\r\n");
printf("按下KEY_UP按键继续!\r\n\r\n\r\n");
//注:%x表示打印十六进制数
// %#x表示在打印的十六进制数前面加上 0x
while(KEY_Scan(0)!=4)
delay_ms(10); //等待KEY_UP按键按下
列表 TestList 的地址为 0x200000b8, 列表 TestList 的索引指针 pxIndex 指向地址 0x200000c0, 列表 TestList 的最后一个列表项地址为 0x200000c0。初始化时列表中还没有列表项,所以索引指针 pxIndex 和 xListEnd 指向的地址一致,都表示指向自己。列表项 ListItem1、ListItem2、ListItem3 的初始化地址分别为 0x200000cc、0x200000e0、0x200000f4。
第三步向列表中添加列表项 ListItem1:
//第三步:向列表TestList添加列表项ListItem1,并通过串口打印所有
//列表项中成员变量pxNext和pxPrevious的值,通过这两个的值观察列表
//项在列表中的连接情况
vListInsert(&TestList,&ListItem1); //插入列表项ListItem1
printf("/******************添加列表项ListItem1*****************/\r\n");
printf("项目 地址 \r\n");
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("/*******************前后向连接分割线********************/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("/************************结束**************************/\r\n");
printf("按下KEY_UP按键继续!\r\n\r\n\r\n");
while(KEY_Scan(0)!=4)
{
delay_ms(10); //等待KEY_UP按键按下
}
ListItem1.xItemValue=40; // ListItem1列表项值为40
ListItem2.xItemValue=60; // ListItem2列表项值为60
ListItem3.xItemValue=50; // ListItem3列表项值为50
列表项是按照列表项的值递增有序排列的;
当插入列表项 ListItem1 以后,列表中存在一个列表项 ListItem1 ,因为列表是环形的,所以会形成列表和列表项 1 的双向循环;此时列表最后一个列表项 xListEnd 的后继指针 pxNext 一定是指向列表项 1,地址也就是 0x200000cc,列表项 1 的后继指针 pxNext 一定是指向列表的最后一个列表项 xListEnd,地址也就是 0x200000cc。列表最后一个列表项 xListEnd 的前驱指针一定是指向列表项 1,其地址为 0x200000cc,列表项 1 的前驱指针一定是指向列表的最后一个列表项 pxPrevious,其地址为 0x200000c0.
第四步向列表中添加列表项 ListItem2:
//第四步:向列表TestList添加列表项ListItem2,并通过串口打印所有
//列表项中成员变量pxNext和pxPrevious的值,通过这两个值观察列表
//项在列表中的连接情况。
vListInsert(&TestList,&ListItem2); //插入列表项ListItem2
printf("/******************添加列表项ListItem2*****************/\r\n");
printf("项目 地址 \r\n");
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("ListItem2->pxNext %#x \r\n",(int)(ListItem2.pxNext));
printf("/*******************前后向连接分割线********************/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("ListItem2->pxPrevious %#x \r\n",(int)(ListItem2.pxPrevious));
printf("/************************结束**************************/\r\n");
printf("按下KEY_UP键继续!\r\n\r\n\r\n");
while(KEY_Scan(0)!=4)
delay_ms(10); //等待KEY_UP键按下
ListItem1.xItemValue=40; // ListItem1列表项值为40
ListItem2.xItemValue=60; // ListItem2列表项值为60
ListItem3.xItemValue=50; // ListItem3列表项值为50
列表项是按照列表项的值递增有序排列的;
当插入列表项 ListItem2 以后,此时列表中存在两个列表项 ListItem1 和 ListItem2;此时列表的最后一个列表项和列表项 1 以及列表项 2 形成环形结构。
列表的最后一个列表项 xListEnd 的后继指针依然指向列表项 1 ,其地址为 0x200000cc,列表项 1 的后继指针指向列表项 2 ,其地址为 0x200000e0,列表 2 的后继指针应该指向列表的最后一个列表项 xListEnd ,其地址为 0x200000cc;列表的最后一个列表项 xListEnd 的前驱指针应该指向列表项 2 ,其地址为 0x200000c0,列表项 1 的前驱指针应该指向列表的最后一个列表项 xListEnd ,其地址为 0x200000c0,列表项 2 的前驱指针应该指向列表项 1 ,其地址为 0x200000cc。
第五步向列表中添加列表项 ListItem3:
//第五步:向列表TestList添加列表项ListItem3,并通过串口打印所有
//列表项中成员变量pxNext和pxPrevious的值,通过这两个值观察列表
//项在列表中的连接情况。
vListInsert(&TestList,&ListItem3); //插入列表项ListItem3
printf("/******************添加列表项ListItem3*****************/\r\n");
printf("项目 地址 \r\n");
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("ListItem3->pxNext %#x \r\n",(int)(ListItem3.pxNext));
printf("ListItem2->pxNext %#x \r\n",(int)(ListItem2.pxNext));
printf("/*******************前后向连接分割线********************/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("ListItem3->pxPrevious %#x \r\n",(int)(ListItem3.pxPrevious));
printf("ListItem2->pxPrevious %#x \r\n",(int)(ListItem2.pxPrevious));
printf("/************************结束**************************/\r\n");
printf("按下KEY_UP键继续!\r\n\r\n\r\n");
while(KEY_Scan(0)!=4)
delay_ms(10); //等待KEY_UP键按下
ListItem1.xItemValue=40; // ListItem1列表项值为40
ListItem2.xItemValue=60; // ListItem2列表项值为60
ListItem3.xItemValue=50; // ListItem3列表项值为50
列表项是按照列表项的值递增有序排列的;注意此时列表项的顺序应该是 xListEnd、ListItem1、ListItem3、ListItem2;
当插入列表项 ListItem3 以后,此时列表中存在三个列表项 ListItem1、ListItem2 以及 ListItem3 ;此时列表的最后一个列表项和列表项 1 、列表项 2 以及 列表项 3 形成环形结构。
列表的最后一个列表项的后继指针依然指向列表项 1,其地址为 0x200000cc,列表项 1 的后继指针应该指向列表项 3 ,其地址为 0x200000f4 ,列表项 3 的后继指针应该指向列表项 2,其地址为 0x200000e0,列表项 2 的后继指针应该指向 xListEnd,其地址为 0x200000c0;列表最后一个列表项的前驱指针应该指向列表项 2 ,其地址为 0x200000e0,列表项 3 的前驱指针应该指向列表项 1 ,其地址为 0x200000cc,列表项 2 的前驱指针应该指向列表项 3 ,其地址为 0x200000f4;
第六步删除 ListItem2:
//第六步:删除ListItem2,并通过串口打印所有列表项中成员变量pxNext和
//pxPrevious的值,通过这两个值观察列表项在列表中的连接情况。
uxListRemove(&ListItem2);
printf("/******************删除列表项ListItem2*****************/\r\n");
printf("项目 地址 \r\n");
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("ListItem3->pxNext %#x \r\n",(int)(ListItem3.pxNext));
printf("/*******************前后向连接分割线********************/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("ListItem3->pxPrevious %#x \r\n",(int)(ListItem3.pxPrevious));
printf("/************************结束**************************/\r\n");
printf("按下KEY_UP键继续!\r\n\r\n\r\n");
while(KEY_Scan(0)!=4)
delay_ms(10); //等待KEY_UP键按下
ListItem1.xItemValue=40; // ListItem1列表项值为40
ListItem2.xItemValue=60; // ListItem2列表项值为60
ListItem3.xItemValue=50; // ListItem3列表项值为50
列表项是按照列表项的值递增有序排列的;注意此时列表项的顺序应该是 xListEnd、ListItem1、ListItem3;
此时,列表的最后一个列表项 xListEnd 的后继指针应该指向列表项 listItem1 ,其地址为 0x200000cc,列表项 1 的后继指针应该指向列表项 3,其地址为 0x200000f4,列表项 3 的后继指针应该指向 xListEnd ,其地址为 0x200000c0; 列表的最后一个列表项 xListEnd 的前驱指针应该指向列表项 listItem3 ,其地址为 0x200000f4,列表项 1 的前驱指针应该指向 xListEnd,其地址为 0x200000c0,列表项 3 的前驱指针应该指向列表项 1,其地址为 0x200000cc;
第七步在末尾添加列表项 ListItem2:
//第七步:在末尾添加列表项ListItem2,并通过串口打印所有列表项中成员变量pxNext和
//pxPrevious的值,通过这两个值观察列表项在列表中的连接情况。
TestList.pxIndex=TestList.pxIndex->pxNext; //pxIndex向后移一项,这样pxIndex就会指向ListItem1。
vListInsertEnd(&TestList,&ListItem2); //列表末尾添加列表项ListItem2
printf("/***************在末尾添加列表项ListItem2***************/\r\n");
printf("项目 地址 \r\n");
printf("TestList->pxIndex %#x \r\n",(int)TestList.pxIndex);
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem2->pxNext %#x \r\n",(int)(ListItem2.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("ListItem3->pxNext %#x \r\n",(int)(ListItem3.pxNext));
printf("/*******************前后向连接分割线********************/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem2->pxPrevious %#x \r\n",(int)(ListItem2.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("ListItem3->pxPrevious %#x \r\n",(int)(ListItem3.pxPrevious));
printf("/************************结束**************************/\r\n\r\n\r\n");
ListItem1.xItemValue=40; // ListItem1列表项值为40
ListItem2.xItemValue=60; // ListItem2列表项值为60
ListItem3.xItemValue=50; // ListItem3列表项值为50
列表项是按照列表项的值递增有序排列的;注意此时列表项的顺序应该是 xListEnd、ListItem2、ListItem1、ListItem3;
在还没有在进行该项操作之前,也就是还未在末尾添加列表项 ListItem2 的时候,列表 TestList 的索引指针 pxIndex 指向列表项 1;列表的索引指针 pxIndex 还是指向列表项 1,其地址为 0x200000cc,列表的最后一个列表项的后继指针应该指向列表项 2 ,其地址也就是 0x200000e0,列表项 2 的后继指针应该指向列表项 1,其地址为 0x20000cc,列表项 1 的后继指针应该指向列表项 3 ,其地址为0x200000f4,列表项 3 的后继指针应该指向 xListEnd,其地址为 0x200000e0;
列表的最后一个列表项的前驱指针应该指向列表项 3 ,其地址为 0x200000f4,列表项 2 的前驱指针应该指向 xListEnd,其地址为 0x200000e0,列表项 1 的前驱指针应该指向列表项 2 ,其地址为 0x20000e0,列表项 3 的前驱指针应该指向列表项 1 ,其地址为 0x200000cc。
7.2 实验程序
#include "stm32f4xx.h"
#include "FreeRTOS.h" //这里注意必须先引用FreeRTOS的头文件,然后再引用task.h
#include "task.h" //存在一个先后的关系
#include "LED.h"
#include "LCD.h"
#include "key.h"
#include "usart.h"
#include "delay.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define TASK1_TASK_PRIO 2
//任务堆栈大小
#define TASK1_STK_SIZE 128
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);
//任务优先级
#define LIST_TASK_PRIO 3
//任务堆栈大小
#define LIST_STK_SIZE 128
//任务句柄
TaskHandle_t ListTask_Handler;
//任务函数
void list_task(void *pvParameters);
//定义一个测试用的列表和3个列表项
//也就是定义四个结构体变量,对应的结构体均在 List.c 和 List.h 中定义
List_t TestList; //测试用列表
ListItem_t ListItem1; //测试用列表项1
ListItem_t ListItem2; //测试用列表项2
ListItem_t ListItem3; //测试用列表项3
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); //设置系统中断优先级分组4
delay_init(168);
uart_init(115200);
LED_Init();
LCD_Init();
KEY_Init();
POINT_COLOR=RED;
LCD_ShowString(30,10,200,16,16,"ATK STM32F407");
LCD_ShowString(30,30,200,16,16,"FreeRTOS Test");
LCD_ShowString(30,50,200,16,16,"List and ListItem");
LCD_ShowString(30,70,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,90,200,16,16,"2023/06/24");
//创建开始任务
xTaskCreate((TaskFunction_t)start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建TASK1任务
xTaskCreate((TaskFunction_t)task1_task, //任务函数
(const char* )"task1_task", //任务名称
(uint16_t )TASK1_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )TASK1_TASK_PRIO, //任务优先级
(TaskHandle_t* )&Task1Task_Handler); //任务句柄
//创建LIST任务
xTaskCreate((TaskFunction_t)list_task, //任务函数
(const char* )"list_task", //任务名称
(uint16_t )LIST_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )LIST_TASK_PRIO, //任务优先级
(TaskHandle_t* )&ListTask_Handler); //任务句柄
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //离开临界区
}
//task1任务函数
void task1_task(void *pvParameters)
{
while(1)
{
LED0=!LED0;
vTaskDelay(500); //延时500ms,也就是500个时钟节拍
}
}
//List任务函数
void list_task(void *pvParameters)
{
//第一步:初始化列表和列表项
vListInitialise(&TestList);
vListInitialiseItem(&ListItem1);
vListInitialiseItem(&ListItem2);
vListInitialiseItem(&ListItem3);
ListItem1.xItemValue=40; //ListItem1列表项值为40
ListItem2.xItemValue=60; //ListItem2列表项值为60
ListItem3.xItemValue=50; //ListItem3列表项值为50
//第二步:打印列表和其他列表项的地址
printf("/*******************列表和列表项地址*******************/\r\n");
printf("项目 地址 \r\n");
printf("TestList %#x \r\n",(int)&TestList);
printf("TestList->pxIndex %#x \r\n",(int)TestList.pxIndex);
printf("TestList->xListEnd %#x \r\n",(int)(&TestList.xListEnd));
printf("ListItem1 %#x \r\n",(int)&ListItem1);
printf("ListItem2 %#x \r\n",(int)&ListItem2);
printf("ListItem3 %#x \r\n",(int)&ListItem3);
printf("/************************结束**************************/\r\n");
printf("按下KEY_UP按键继续!\r\n\r\n\r\n");
//注:%x表示打印十六进制数
// %#x表示在打印的十六进制数前面加上 0x
while(KEY_Scan(0)!=4)
delay_ms(10); //等待KEY_UP按键按下
//第三步:向列表TestList添加列表项ListItem1,并通过串口打印所有
//列表项中成员变量pxNext和pxPrevious的值,通过这两个的值观察列表
//项在列表中的连接情况
vListInsert(&TestList,&ListItem1); //插入列表项ListItem1
printf("/******************添加列表项ListItem1*****************/\r\n");
printf("项目 地址 \r\n");
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("/*******************前后向连接分割线********************/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("/************************结束**************************/\r\n");
printf("按下KEY_UP按键继续!\r\n\r\n\r\n");
while(KEY_Scan(0)!=4)
{
delay_ms(10); //等待KEY_UP按键按下
}
//第四步:向列表TestList添加列表项ListItem2,并通过串口打印所有
//列表项中成员变量pxNext和pxPrevious的值,通过这两个值观察列表
//项在列表中的连接情况。
vListInsert(&TestList,&ListItem2); //插入列表项ListItem2
printf("/******************添加列表项ListItem2*****************/\r\n");
printf("项目 地址 \r\n");
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("ListItem2->pxNext %#x \r\n",(int)(ListItem2.pxNext));
printf("/*******************前后向连接分割线********************/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("ListItem2->pxPrevious %#x \r\n",(int)(ListItem2.pxPrevious));
printf("/************************结束**************************/\r\n");
printf("按下KEY_UP键继续!\r\n\r\n\r\n");
while(KEY_Scan(0)!=4)
delay_ms(10); //等待KEY_UP键按下
//第五步:向列表TestList添加列表项ListItem3,并通过串口打印所有
//列表项中成员变量pxNext和pxPrevious的值,通过这两个值观察列表
//项在列表中的连接情况。
vListInsert(&TestList,&ListItem3); //插入列表项ListItem3
printf("/******************添加列表项ListItem3*****************/\r\n");
printf("项目 地址 \r\n");
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("ListItem3->pxNext %#x \r\n",(int)(ListItem3.pxNext));
printf("ListItem2->pxNext %#x \r\n",(int)(ListItem2.pxNext));
printf("/*******************前后向连接分割线********************/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("ListItem3->pxPrevious %#x \r\n",(int)(ListItem3.pxPrevious));
printf("ListItem2->pxPrevious %#x \r\n",(int)(ListItem2.pxPrevious));
printf("/************************结束**************************/\r\n");
printf("按下KEY_UP键继续!\r\n\r\n\r\n");
while(KEY_Scan(0)!=4)
delay_ms(10); //等待KEY_UP键按下
//第六步:删除ListItem2,并通过串口打印所有列表项中成员变量pxNext和
//pxPrevious的值,通过这两个值观察列表项在列表中的连接情况。
uxListRemove(&ListItem2);
printf("/******************删除列表项ListItem2*****************/\r\n");
printf("项目 地址 \r\n");
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("ListItem3->pxNext %#x \r\n",(int)(ListItem3.pxNext));
printf("/*******************前后向连接分割线********************/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("ListItem3->pxPrevious %#x \r\n",(int)(ListItem3.pxPrevious));
printf("/************************结束**************************/\r\n");
printf("按下KEY_UP键继续!\r\n\r\n\r\n");
while(KEY_Scan(0)!=4)
delay_ms(10); //等待KEY_UP键按下
//第七步:在末尾添加列表项ListItem2,并通过串口打印所有列表项中成员变量pxNext和
//pxPrevious的值,通过这两个值观察列表项在列表中的连接情况。
TestList.pxIndex=TestList.pxIndex->pxNext; //pxIndex向后移一项,这样pxIndex就会指向ListItem1。
vListInsertEnd(&TestList,&ListItem2); //列表末尾添加列表项ListItem2
printf("/***************在末尾添加列表项ListItem2***************/\r\n");
printf("项目 地址 \r\n");
printf("TestList->pxIndex %#x \r\n",(int)TestList.pxIndex);
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem2->pxNext %#x \r\n",(int)(ListItem2.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("ListItem3->pxNext %#x \r\n",(int)(ListItem3.pxNext));
printf("/*******************前后向连接分割线********************/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem2->pxPrevious %#x \r\n",(int)(ListItem2.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("ListItem3->pxPrevious %#x \r\n",(int)(ListItem3.pxPrevious));
printf("/************************结束**************************/\r\n\r\n\r\n");
while(1)
{
LED1=!LED1;
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
}
}