【07】FreeRTOS的列表和列表项

news2024/10/6 22:31:26

目录

1.列表和列表项的简介

1.1列表结构体成员-介绍

1.2列表项结构层成员-介绍 

1.3迷你列表项

1.4列表和列表项的关系

2.列表相关API函数介绍

2.1列表初始化函数vListInitialise()

2.2列表项初始化函数vListInitialiseItem()

2.3列表项插入函数vListInsert()

2.4末尾插入列表项函数vListInsertEnd()

2.5删除列表项函数uxListRemove()

3.列表项的插入和删除实验

4.总结


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、成员变量 pxNextpxPrevious 分别用于指向列表中列表项的下一个列表项和上一个列表项;

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、成员变量 pxNextpxPrevious 分别用于指向列表中列表项的下一个列表项和上一个列表项;

3、迷你列表项只用于标记列表的末尾(迷你列表项永远排在列表末尾)和挂载其他插入列表中的列表项(当列表中没有其他列表项时,新插入的列表项和迷你列表项互相指向),因此不需要成员变量 pxOwnerpxContainer,以节省内存开销。

迷你列表项结构示意图:

1.4列表和列表项的关系

 列表初始状态如下:

        初始化列表时,由于只有末尾列表项且不算数,所以uxNumberOfItems等于0;由于只有末尾列表项,则pxIndex指向末尾列表项(pxIndex指向某一个列表项,而此时只有末尾列表项);末尾列表项只在末尾,则其列表项值xItemValue最大,排在最后,pxPrevious指向上一个列表项,pxNext指向下一个列表项,由于一开始列表中没有其他列表项,所以pxPreviouspxNext会指向自己。

        上图有两个待插入的列表项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.总结

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/194518.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微服务配置中心, 这个方案 Go 里用起来不输SpringCloud

微服务架构设计模式里有一条讲到&#xff0c;要设计可配置的服务。把服务从单体架构细分成微服务后&#xff0c;所有配置属性都集中存储在一个位置&#xff0c;更易于管理。这个集中存储管理配置的地方叫&#xff0c;就是配置中心。 使用配置中心还有一个好处就是&#xff0c;…

java基础面试题 一

一、面向对象五大基本原则是什么 1.单一职责原则SRP(Single Responsibility Principle) 类的功能要单一&#xff0c;不能包罗万象&#xff0c;跟杂货铺似的。 2.开放封闭原则OCP(Open&#xff0d;Close Principle) 一个模块对于扩展是开放的&#xff0c;对于修改是封闭的 …

Jenkins基于docker cloud动态增减节点

jenkins可以按照jenkins容器启动去部署 Jenkins管理动态节点 动态节点可以在有job运行时&#xff0c;临时加入一个agent到jenkins master&#xff0c;然后等job执行完毕之后&#xff0c;所加入的agent再自动删除掉&#xff0c;达到一个动态的增删节点效果&#xff0c;使所有的…

引用第三方插件到分包中即如何把uni_modules文件夹中的插件放入分包中

网上搜索了很多&#xff0c;但都没有直接说明如何把node_modules中的第三方插件如何引入到分包中&#xff0c;首先为什么要在分包引入&#xff0c;原因就是小程序有包大小的限制&#xff0c;不能超过2M&#xff0c;超过2M的话&#xff0c;则不能发布或预览&#xff0c;于是不能…

SCT81620QSTER,升降压电源芯片

P2P替代 LM3478和LM3481SCT81620QSTER设备是一个宽输入、非同步升压控制器。该设备可用于增强、间隔和反馈转换器和拓扑结构。SCT81620Q设备的开关频率可以通过将SCT81620Q设备的开关频率调整到100 kHz到2.2MHz之间的任何值。电流模式控制提供了优越的带宽和瞬态响应&#xff0…

IDEA设置注释模板(详细版)

IDEA设置注释模板 类注释模板方法注释模板效果展示 1. 类注释模板 类注释模板是IDEA创建类时生成的注释 第一步、File -> Settings 第二步、Editor -> File and Code Templates -> Includes -> File Header 点apply&#xff0c;再点ok即可 模板参考如下&#…

拉格朗日插值原理及其Julia实现

文章目录数学原理算法化测试设函数yf(x)yf(x)yf(x)在区间[a,b][a,b][a,b]上有定义&#xff0c;且在点a⩽x0⩽x1…⩽xn⩽ba\leqslant x_0\leqslant x_1\ldots\leqslant x_n\leqslant ba⩽x0​⩽x1​…⩽xn​⩽b上的值y0,y1…yny_0, y_1 \ldots y_ny0​,y1​…yn​之间存在一个函…

规则引擎-drools-3.2-drl文件构成-rule部分-属性Attribute

文章目录drl文件构成-rule部分rule示例rule nameAttribute全部属性说明no-loop 和 lock-on-activeactivation-group 和 agenda-groupdrl文件构成-rule部分 drl文件构成&#xff0c;位于官网的第5章位置&#xff0c;也是drools作为规则引擎应用的最核心部分。 其中rule模块&…

谷歌浏览器无法翻译此网页,解决方法?(谷歌浏览器无法翻译成中文,谷歌翻译,最新方法)

谷歌浏览器自带的翻译功能,对我们来说用处很大,但有的时候突然就会变成“无法翻译此网页”,针对此问题这里提供几种解决方案(翻译插件),如下: 方法1: 蓝奏云文件https://wwot.lanzouw.com/iFc7d0hmrtpg 访问密码:slee 方法2: 脚本之家

分布式事务的4种模式

分布式事务理论基础 解决分布式事务&#xff0c;也有相应的规范和协议。分布式事务相关的协议有2PC、3PC。 由于三阶段提交协议3PC非常难实现&#xff0c;目前市面主流的分布式事务解决方案都是2PC协议。 有些文章分析2PC时&#xff0c;几乎都会用TCC两阶段的例子&#xff0…

Java开发基础入门之Java基础中的Stack类及其常用方法

一、Stack类 1.Stack是Vector的一个子类&#xff0c;它实现标准的后进先出堆栈。Stack只定义了创建空堆栈的默认构造方法。 Stack() 2.Stack类里面主要实现的有以下的几个方法&#xff1a; (1)boolean empty( )方法是判断堆栈是否为空。 (2)Object peek( )方法是返回栈顶端…

一夜爆火的现象级产品ChatGPT,是AI突破还是昙花乍现?

导语 | 编写代码、翻译小说、参加考试……2022 年末&#xff0c;人工智能聊天机器人 ChatGPT 风靡全网。自 2016 年 AlphaGo 击败围棋世界冠军李世石后&#xff0c;ChatGPT 再次掀起了人工智能发展应用的高潮。它将会给我们带来哪些影响&#xff1f;人工智能的颠覆性应用是否即…

MyBatis 二级缓存整合Redis【学习记录】

二级缓存整合Redis 上篇文章介绍了MyBatis自带的二级缓存&#xff0c;但是这个缓存是单服务器工作&#xff0c;无法实现分布式缓存。那么什么是分布式缓存呢&#xff1f;假设现在有两个服务器1和2&#xff0c;用户访问的时候访问了服务器1&#xff0c;查询后的缓存就会放在服务…

酒店预订订单的分析与建模【决策树、xgboost】

酒店预订订单的分析与建模【决策树、xgboost】 本项目包含 1.数据处理 2.数据探索性分析 3.网格搜索对决策树、xgboost进行模型参数调优 4.基于五折交叉验证的决策树、xgboost模型预测 专栏和往期项目 &#x1f449;往期文章可以关注我的专栏 下巴同学的数据加油小站 会不…

《神经网络与深度学习》 邱希鹏 学习笔记(二)

正则化 所有损害优化的方法都是正则化。增加优化约束&#xff0c;干扰优化过程 优化约束包括 L1/L2约束&#xff0c;数据增强 干扰优化包括 随机梯度下降 权重衰减 提前停止 在上式中 y(n)为样本n&#xff0c;其展开形式为y^{(n)}为样本n&#xff0c;其展开形式为y(n)为样本…

【Axure教程】低代码可视化编辑器

低代码是一组数字技术工具平台&#xff0c;基于图形化拖拽、参数化配置等更为高效的方式&#xff0c;通过少量代码或不用代码实现数字化转型中的场景应用创新。例如在业务系统中&#xff0c;如果企业新增了一项业务&#xff0c;以往往往需要对系统继续开发和升级&#xff0c;但…

【Python学习笔记】9. Python3 解释器

前言 Linux/Unix的系统上&#xff0c;一般默认的 python 版本为 2.x&#xff0c;我们可以将 python3.x 安装在 /usr/local/python3 目录中。 Python3 解释器 Linux/Unix的系统上&#xff0c;一般默认的 python 版本为 2.x&#xff0c;我们可以将 python3.x 安装在 /usr/loca…

IDEA 常用插件跟配置提升开发效率

工欲善其事必先利其器 安装好 IntelliJ IDEA 后&#xff0c;进行如下的初始化操作&#xff0c;工作效率提升50倍。 一、插件 1. Codota 代码智能提示插件 只要打出首字母就能联想出一整条语句&#xff0c;这也太智能了&#xff0c;还显示了每条语句使用频率。原因是它学习了…

最全的视频转换器工具清单,这18款免费视频格式转换器记得收藏

审查和比较具有功能和定价的最佳视频转换器软件。从这个顶级付费和免费在线视频转换器工具列表中选择&#xff0c;以快速轻松地转换任何视频&#xff1a; 什么是视频转换器&#xff1f; 视频转换工具允许您将视频从一种格式转换为另一种格式。第一个商业上成功的视频格式是 Q…

11.1-股票基金历年收益率计算

文章目录1. 计算目标2. 关键问题3. 获取交易日历4. 逻辑编写1. 计算目标 我们想知道&#xff0c;一只股票标的&#xff0c;在之前的几年中&#xff0c;每一年的年化收益率是多少&#xff1f; 如果将每年的年化收益率进行求和汇总&#xff0c;截止到今年&#xff0c;总共年化收…