目录
2.2列表项初始化函数vListInitialiseItem()
1.列表和列表项的简介
列表是 FreeRTOS 中的一个数据结构,概念上和链表有点类似,列表被用来跟踪 FreeRTOS中的任务。列表有阻塞、就绪、挂起列表等,这些列表用来表示任务的状态。
列表项就是存放在列表中的项目。列表项意思是列表的子集,放在列表中的项目。列表类似于链表,也有节点,列表项就是其节点,节点用前一个列表项指向下一个列表项进行连接,末尾的指向开始的列表项,类似于环形链表。但是列表是双向的,可以获得前一个列表项。列表项可以理解为存放数据的,一个数据下一个next上一个prev指向谁,还有一个数据代表哪一个任务。
列表相当于链表,列表项相当于节点,FreeRTOS 中的列表是一个双向环形链表。
举个例子:小明、小红、小黑在玩手拉手的游戏,左手代表下一个next,右手代表上一个prev,形成了一个湖岸线列表。假设小明和小黑不想和小黑玩,则小明的左手直接抓到小黑的右手,则小红被踢出游戏,可以删除小红此列表项(列表可以随意删除其列表项);假设小明、小红、小黑三人在玩游戏,小黄想要加入,小黄左手拉小黑右手、右手拉小红左手,加入列表(插入列表项)。
列表的特点:列表项间的地址非连续的(地址连不连续无所谓,是靠上下两双手连接,和数组有区别,数组是连续的地址),是人为的连接到一起的。列表项的数目是由后期添加的个数决定的,随时可以改变。
数组的特点:数组成员地址是连续的,数组在最初确定了成员数量后期无法改变
在OS中任务的数量是不确定的,并且任务状态是会发生改变的,所以非常适用列表(链表)这种数据结构。
1.1列表结构体成员-介绍
有关于列表的东西均在文件 list.c 和 list.h 中(属于FreeRTOS的源码),首先我们先看下在list.h中的,列表相关结构体:
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、在该结构体中, 包含了两个宏,这两个宏是确定的已知常量, FreeRTOS通过检查这两个常量的值, 来判断列表的数据在程序运行过程中,是否遭到破坏 ,该功能一般用于调试, 默认是不开启的(没有用到);
2、成员uxNumberOfItems,用于记录列表中列表项的个数(不包含末尾列表项xListEnd);
3、成员pxIndex用于指向列表中的某个列表项,一般用于遍历列表中的所有列表项;
4、成员变量 xListEnd是一个迷你列表项,总是排在最末尾;
列表项结构示例图:
校验值不用看;uxNumberOfItems用来记录列表项的数目,但是没有算上迷你列表项xListEnd;pxIndex总是指向列表中的某个列表项;xListEnd是一个末尾列表项,末尾列表项中有三个成员变量。
1.2列表项结构层成员-介绍
列表项是列表中用于存放数据的地方,在 list.h 文件中,有列表项的相关结构体定义:
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 /* 列表项的拥有者 */
struct xLIST * configLIST_VOLATILE pxContainer; /* 列表项所在列表 */
listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE /* 用于检测列表项的数据完整性 */
};
typedef struct xLIST_ITEM ListItem_t;
首末两项可以不用看,用于检测数据完整性,并没有使用此功能。
1、成员变量 xItemValue 为列表项的值,这个值多用于按升序对列表中的列表项进行排序(例如有一个列表,列表中存在三个列表项,列表项1的xItemValue 值为10、列表项2的xItemValue 值为20、列表项2的xItemValue 值为30。此时有一个列表项4xItemValue值为25,则插入到列表项2和3之间,组成列表项的升序排列。阻塞列表也是同样的道理,阻塞20ms、32ms的值存入变量xItemValue中,按升序排列,优先处理阻塞20ms的列表项);
2、成员变量 pxNext 和 pxPrevious 分别用于指向列表中列表项的下一个列表项和上一个列表项;
3、成员变量 pxOwner 用于指向包含列表项的对象(通常是任务控制块,指向某一个任务);
4、成员变量 pxContainer 用于指向列表项所在列表(就绪、阻塞、挂起,指向某一个状态)。
列表项结构示意图:
xItemValue为列表项的数值,主要用于列表项的升序排列;pxOwner 指向列表项指向哪一个任务控制块(属于哪一个任务);pxContainer 指向列表项属于哪一个列表;pxPrevious 指向上一个列表项;pxNext 指向下一个列表项。列表项是用来存放数据的。
1.3迷你列表项
迷你列表项也是列表项,但迷你列表项仅用于标记列表的末尾和挂载其他插入列表中的列表项。
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,以节省内存开销。
迷你列表项结构示意图:
1.4列表和列表项的关系
列表初始状态如下:
初始化列表时,由于只有末尾列表项且不算数,所以uxNumberOfItems等于0;由于只有末尾列表项,则pxIndex指向末尾列表项(pxIndex指向某一个列表项,而此时只有末尾列表项);末尾列表项只在末尾,则其列表项值xItemValue最大,排在最后,pxPrevious指向上一个列表项,pxNext指向下一个列表项,由于一开始列表中没有其他列表项,所以pxPrevious和pxNext会指向自己。
上图有两个待插入的列表项1和列表项2, 值分别为10和20。首先插入列表项1,则末尾列表项将不再指向自己,列表项1和末尾列表项互相指向,uxNumberOfItems变为1。插入列表项2,列表项2的xItemValue值为20,20比10大,则列表项2排在列表项1的后面,末尾列表项的pxNext指向列表项1,列表项1的pxNext指向列表项2,列表项2的pxNext指向末尾列表项;同理,pxPrevious是相同的;uxNumberOfItems变为2。结果如下图所示:
2.列表相关API函数介绍
函数 | 描述 |
---|---|
vListInitialise() | 初始化列表(列表的初始状态通过此函数得到) |
vListInitialiseItem() | 初始化列表项(初始化,是对列表、列表项结构体中的变量赋一个初始值) |
vListInsertEnd() | 列表末尾插入列表项 |
vListInsert() | 列表插入列表项(升序插入,根据列表项的数值xItemValue进行列表升序插入) |
uxListRemove() | 列表移除列表项 |
参考资料: 正点原子《FreeRTOS开发手册》第七章 ——“FreeRTOS列表和列表项”。建议至少看完一遍。
2.1列表初始化函数vListInitialise()
void vListInitialise(List_t * const pxList){
/* 初始化时,列表中只有 xListEnd,因此 pxIndex 指向 xListEnd */
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
/* xListEnd 的值初始化为最大值,用于列表项升序排序时,排在最后 */
pxList->xListEnd.xItemValue = portMAX_DELAY;
/* 初始化时,列表中只有 xListEnd,因此上一个和下一个列表项都为 xListEnd 本身 */
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );
/* 初始化时,列表中的列表项数量为 0(不包含 xListEnd) */
pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
/* 初始化用于检测列表数据完整性的校验值 */
listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}
参数介绍:
pxList:待初始化列表
初始化后列表结构:
2.2列表项初始化函数vListInitialiseItem()
void vListInitialiseItem( ListItem_t * const pxItem )
{
/* 初始化时,列表项所在列表设为空 */
pxItem->pxContainer = NULL;
/* 初始化用于检测列表项数据完整性的校验值 */
listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}
参数介绍:
pxItem:待初始化列表项
pxContainer参数表示列表项所属于哪一个列表,初始化列表项时是不属于任何列表的,所以设置为空。
初始化后的列表项结构:
2.3列表项插入函数vListInsert()
void vListInsert ( List_t * const pxList , ListItem_t * const pxNewListItem )
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; /* 插入的位置为列表 xListEnd 前面 */
} 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->pxContainer = pxList; /* 更新待插入列表项所在列表 */
( pxList->uxNumberOfItems )++; /* 更新列表中列表项的数量 */
}
参数介绍:
pxList :要插入的目标列表;
pxNewListItem:待插入的列表项,将列表项插入到目标列表中,并且按列表项的值进行升序排列;
此函数用于将待插入列表的列表项按照列表项值升序进行排序,有序地插入到列表中。在插入过程中,如果列表项的值等于末尾列表项的值,则插入到末尾列表项前面; 否则遍历列表,找到要插入的位置。
2.4末尾插入列表项函数vListInsertEnd()
void vListInsertEnd ( List_t * const pxList , ListItem_t * const pxNewListItem )
{
省略部分非关键代码 … …
/* 获取列表 pxIndex 指向的列表项 */
ListItem_t * const pxIndex = pxList->pxIndex;
/* 更新待插入列表项的指针成员变量 */
pxNewListItem->pxNext = pxIndex;
pxNewListItem->pxPrevious = pxIndex->pxPrevious;
/* 更新列表中原本列表项的指针成员变量 */
pxIndex->pxPrevious->pxNext = pxNewListItem;
pxIndex->pxPrevious = pxNewListItem;
/* 更新待插入列表项的所在列表成员变量 */ pxNewListItem->pxContainer = pxList;
/* 更新列表中列表项的数量 */
( pxList->uxNumberOfItems )++;
}
参数介绍:
pxList :要插入的目标列表;
pxNewListItem :待插入的列表项,将列表项插入到目标列表中;
此函数并不是真正的将待插入的列表项插入到目标列表末尾,而是根据pxIndex指针,将待插入的列表项插入到pxIndex指针指向的列表项的前一个位置,是一种无序的插入方法。
2.5删除列表项函数uxListRemove()
UBaseType_t uxListRemove ( ListItem_t * const pxItemToRemove )
{
/* 获取待删除列表项所属列表 */
List_t * const pxList = pxItemToRemove->pxContainer;
/* 从列表中移除列表项 */
pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
/*如果 pxIndex 正指向待移除的列表项 */
if( pxList->pxIndex == pxItemToRemove )
{
/*pxIndex 指向上一个列表项*/
pxList->pxIndex = pxItemToRemove->pxPrevious;
} else
{
mtCOVERAGE_TEST_MARKER();
}
/*将待移除的列表项的所在列表指针清空*/
pxItemToRemove->pxContainer = NULL;
/*更新列表中列表项的数量*/
( pxList->uxNumberOfItems )--;
/*返回移除后的列表中列表项的数量*/
return pxList->uxNumberOfItems;
}
此函数用于将列表项从列表项所在列表中移除。
参数介绍:
pxItemToRemove:待移除的列表项(列表项结构体成员中pxContainer用于表示属于哪一个列表,所以入口参数只有一个,不需要所在列表);
UBaseType_t整数返回值:待移除列表项移除后,所在列表剩余列表项的数量
3.列表项的插入和删除实验
1、实验目的:学会对FreeRTOS 列表和列表项的操作函数使用,并观察运行结果和理论分 析是否一致
2、实验设计:将设计三个任务:start_task、task1、task2
三个任务的功能如下:
start_task:用来创建其他的2个任务
task1:实现LED0每500ms闪烁一次,用来提示系统正在运行
task2:调用列表和列表项相关API函数,并且通过串口输出相应的信息,进行观察
本次实验基于本系列文章中【05】FreeRTOS的中断管理工程文件实现。
修改freertos_demo.c,将创建task2,完成task1程序内容,删除main.c中定时器初始化相关程序。修改后的部分程序如下所示:
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
void task2( void * pvParameters );
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /*进入临界区*/
xTaskCreate((TaskFunction_t ) task1,
(char * ) "task1",
(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIO,
(TaskHandle_t * ) &task1_handler );
xTaskCreate((TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(TaskHandle_t * ) &task2_handler );
vTaskDelete(NULL);
taskEXIT_CRITICAL(); /*退出临界区*/
}
/* 任务1,实现LED0每500ms闪烁一次,用来提示系统正在运行 */
void task1( void * pvParameters )
{
while(1)
{
LED0=~LED0;
vTaskDelay(500);
}
}
/* 任务2,调用列表和列表项相关API函数,并且通过串口输出相应的信息,进行观察 */
void task2( void * pvParameters )
{
while(1)
{
vTaskDelay(500);
}
}
创建列表及列表项,完成task2程序内容,最终完整版freertos_demo.c程序如下所示:
#include "freertos_demo.h"
#include "usart.h"
#include "led.h"
#include "lcd.h"
#include "key.h"
#include "delay.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handler;
void start_task( void * pvParameters );
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
void task2( void * pvParameters );
/******************************************************************************************************/
List_t TestList; /*定义测试列表*/
ListItem_t ListItem1; /*定义测试列表项1*/
ListItem_t ListItem2; /*定义测试列表项2*/
ListItem_t ListItem3; /*定义测试列表项3*/
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
xTaskCreate((TaskFunction_t ) start_task,
(char * ) "start_task",
(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIO,
(TaskHandle_t * ) &start_task_handler );
vTaskStartScheduler();
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /*进入临界区*/
xTaskCreate((TaskFunction_t ) task1,
(char * ) "task1",
(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIO,
(TaskHandle_t * ) &task1_handler );
xTaskCreate((TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(TaskHandle_t * ) &task2_handler );
vTaskDelete(NULL);
taskEXIT_CRITICAL(); /*退出临界区*/
}
/* 任务1,实现LED0每500ms闪烁一次,用来提示系统正在运行 */
void task1( void * pvParameters )
{
while(1)
{
LED0=~LED0;
vTaskDelay(500);
}
}
/* 任务2,调用列表和列表项相关API函数,并且通过串口输出相应的信息,进行观察 */
void task2( void * pvParameters )
{
/* 第一步:初始化列表及列表项 */
vListInitialise(&TestList); /*初始化列表*/
vListInitialiseItem(&ListItem1); /*初始化列表项1*/
vListInitialiseItem(&ListItem2); /*初始化列表项2*/
vListInitialiseItem(&ListItem3); /*初始化列表项3*/
ListItem1.xItemValue=20;
ListItem2.xItemValue=40;
ListItem3.xItemValue=30;
/* 第二步:打印列表和其他列表项的地址 */
printf("/**************第二步:打印列表和列表项的地址**************/\r\n");
printf("项目\t\t\t地址\r\n");
printf("TestList\t\t0x%p\t\r\n", &TestList);
printf("TestList->pxIndex\t0x%p\t\r\n", TestList.pxIndex);
printf("TestList->xListEnd\t0x%p\t\r\n", (&TestList.xListEnd));
printf("ListItem1\t\t0x%p\t\r\n", &ListItem1);
printf("ListItem2\t\t0x%p\t\r\n", &ListItem2);
printf("ListItem3\t\t0x%p\t\r\n", &ListItem3);
printf("/**************************结束***************************/\r\n");
printf("\r\n/*****************第三步:列表项1插入列表******************/\r\n");
vListInsert((List_t* )&TestList, /* 列表 */
(ListItem_t*)&ListItem1); /* 列表项 */
printf("项目\t\t\t\t地址\r\n");
printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));
printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));
printf("/**************************结束***************************/\r\n");
/* 第四步:列表项2插入列表 */
printf("\r\n/*****************第四步:列表项2插入列表******************/\r\n");
vListInsert((List_t* )&TestList, /* 列表 */
(ListItem_t*)&ListItem2); /* 列表项 */
printf("项目\t\t\t\t地址\r\n");
printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));
printf("ListItem2->pxNext\t\t0x%p\r\n", (ListItem2.pxNext));
printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));
printf("ListItem2->pxPrevious\t\t0x%p\r\n", (ListItem2.pxPrevious));
printf("/**************************结束***************************/\r\n");
/* 第五步:列表项3插入列表 */
printf("\r\n/*****************第五步:列表项3插入列表******************/\r\n");
vListInsert((List_t* )&TestList, /* 列表 */
(ListItem_t*)&ListItem3); /* 列表项 */
printf("项目\t\t\t\t地址\r\n");
printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));
printf("ListItem2->pxNext\t\t0x%p\r\n", (ListItem2.pxNext));
printf("ListItem3->pxNext\t\t0x%p\r\n", (ListItem3.pxNext));
printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));
printf("ListItem2->pxPrevious\t\t0x%p\r\n", (ListItem2.pxPrevious));
printf("ListItem3->pxPrevious\t\t0x%p\r\n", (ListItem3.pxPrevious));
printf("/**************************结束***************************/\r\n");
/* 第六步:移除列表项2 */
printf("\r\n/*******************第六步:移除列表项2********************/\r\n");
uxListRemove((ListItem_t* )&ListItem2); /* 移除列表项 */
printf("项目\t\t\t\t地址\r\n");
printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));
printf("ListItem3->pxNext\t\t0x%p\r\n", (ListItem3.pxNext));
printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));
printf("ListItem3->pxPrevious\t\t0x%p\r\n", (ListItem3.pxPrevious));
printf("/**************************结束***************************/\r\n");
/* 第七步:列表末尾添加列表项2 */
printf("\r\n/****************第七步:列表末尾添加列表项2****************/\r\n");
TestList.pxIndex = &ListItem1;
vListInsertEnd((List_t* )&TestList, /* 列表 */
(ListItem_t* )&ListItem2); /* 列表项 */
printf("项目\t\t\t\t地址\r\n");
printf("TestList->pxIndex\t\t0x%p\r\n", TestList.pxIndex);
printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));
printf("ListItem2->pxNext\t\t0x%p\r\n", (ListItem2.pxNext));
printf("ListItem3->pxNext\t\t0x%p\r\n", (ListItem3.pxNext));
printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));
printf("ListItem2->pxPrevious\t\t0x%p\r\n", (ListItem2.pxPrevious));
printf("ListItem3->pxPrevious\t\t0x%p\r\n", (ListItem3.pxPrevious));
printf("/************************实验结束***************************/\r\n");
while(1)
{
vTaskDelay(500);
}
}
实验现象:
列表项之间互相指向的地址与理论部分完全一致。
4.总结