FreeRTOS(3)列表List
在 FreeRTOS 的源码中大量地使用了列表和列表项,因此想要深入学习 FreeRTOS,列表和列表项是必备的基础知识。这里所说的列表和列表项,是 FreeRTOS 源码中 List 和 List Item 的
直译,事实上, FreeRTOS 中的列表和列表项就是数据结构中的链表和节点。这部分的内容并不难,但对于理解 FreeRTOS 相当重要,因此笔者建议读者在对本章内容了解透彻后,再继续下
面章节的学习。
列表和列表项简介
列表(List)
列表是 FreeRTOS 中最基本的一种数据结构,其在物理存储单元上是非连续、非顺序的。列表在 FreeRTOS 中的应用十分广泛,要注意的是, FreeRTOS 中的列表是一个双向链表, 在
list.h 文件中,有列表的相关定义,具体代码如下所示:
typedef struct xLIST
{listFIRST_LIST_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */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,这两个宏用于存放确定已知常量, FreeRTOS
通过检查这两个常量的值,来判断列表的数据在程序运行过程中,是否遭到破坏,类似这样的宏定义在列表项和迷你列表项中也有出现。该功能一般用于调试, 默认是不开启的,因此本教
程暂不讨论这个功能。
成员变量 uxNumberOfItems 用于记录列表中列表项的个数(不包含 xListEnd),当往列表中插入列表项时,该值加 1;当从列表中移除列表项时,该值减 1。
成员变量 pxIndex 用于指向列表中的某个列表项,一般用于遍历列表中的所有列表项。
成员变量 xListEnd 是一个迷你列表项, 列表中迷你列表项的值一般被设置为最大值,用于将列表中的所有列表项按升序排序时,排在最末尾;同时 xListEnd 也用于挂载其他插入到列表中的列表项。
列表的结构示意图,如下图所示:

列表项(List Item)
列表项是列表中用于存放数据的地方,在 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 ascending 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. */struct xLIST * configLIST_VOLATILE pxContainer; /*< 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; /* For some reason lint wants this as two separate definitions. */
如同列表一样,列表项中也包含了两个用于检测列表项数据完整性的宏定义。
成员变量 xItemValue 为列表项的值,这个值多用于按升序对列表中的列表项进行排序。
成员变量 pxNext 和 pxPrevious 分别用于指向列表中列表项的下一个列表项和上一个列表项。
成员变量 pxOwner 用于指向包含列表项的对象(通常是任务控制块),因此,列表项和包含列表项的对象之间存在双向链接。
成员变量 pxContainer 用于指向列表项所在列表。
列表项的结构示意图,如下图所示:

迷你列表项(Mini List Item)
迷你列表项也是列表项,但迷你列表项仅用于标记列表的末尾和挂载其他插入列表中的列表项,用户是用不到迷你列表项的,在 list.h 文件中,有迷你列表项的相关定义,具体的代码如下所示:
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;
迷你列表项中也同样包含用于检测列表项数据完整性的宏定义。
成员变量 xItemValue 为列表项的值,这个值多用于按升序对列表中的列表项进行排序。
成员变量 pxNext 和 pxPrevious 分别用于指向列表中列表项的下一个列表项和上一个列表项。
迷你列表项相比于列表项,因为只用于标记列表的末尾和挂载其他插入列表中的列表项,因此不需要成员变量 pxOwner 和 pxContainer,以节省内存开销。
迷你列表项的结构示意图,如下图所示:

列表和列表项相关 API 函数
FreeRTOS 中列表和列表项相关的 API 函数如下表所示:
| 函数 | 描述 |
| vListInitialise() | 初始化列表 |
| vListInitialiseItem() | 初始化列表项 |
| vListInsertEnd() | 列表末尾插入列表项 |
| vListInsert() | 列表插入列表项 |
| uxListRemove() | 列表移除列表项 |
1、函数 vListInitialise()
此函数用于初始化列表,在定义列表之后,需要先对其进行初始化, 只有初始化后的列表,才能够正常地被使用。列表初始化的过程,其实就是初始化列表中的成员变量。 函数原型如下所示:
void vListInitialise(List_t * const pxList);
| 形参 | 描述 |
| pxList | 待初始化列表 |
函数 vListInitialise()无返回值。
函数 vListInitialise()在 list.c 文件中有定义,具体的代码如下所示:
void vListInitialise( List_t * const pxList )
{/* 链表结构包含一个用于标记链表末尾的链表项。* 初始化链表时,将链表末尾插入为唯一的链表项。 */pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd ); /*lint !e826 !e740 !e9087 使用迷你链表结构作为链表末尾以节省 RAM。这是经过检查且有效的。 */listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( &( pxList->xListEnd ) );/* 链表末尾的值是链表中可能的最大值,* 以确保它始终位于链表的末尾。 */pxList->xListEnd.xItemValue = portMAX_DELAY;/* 链表末尾的 next 和 previous 指针指向自身,* 这样我们可以知道链表何时为空。 */pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd ); /*lint !e826 !e740 !e9087 使用迷你链表结构作为链表末尾以节省 RAM。这是经过检查且有效的。 */pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd ); /*lint !e826 !e740 !e9087 使用迷你链表结构作为链表末尾以节省 RAM。这是经过检查且有效的。 *//* 当 xListEnd 是一个完整的 ListItem_t 时,初始化其剩余的字段 */#if ( configUSE_MINI_LIST_ITEM == 0 ){pxList->xListEnd.pvOwner = NULL;pxList->xListEnd.pxContainer = NULL;listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( &( pxList->xListEnd ) );}#endifpxList->uxNumberOfItems = ( UBaseType_t ) 0U;/* 如果 configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 设置为 1,* 则将已知值写入链表中。 */listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}
函数 vListInitialise()初始化后的列表结构示意图, 如下图所示:

2、函数 vListInitialiseItem()
此函数用于初始化列表项,如同列表一样,在定义列表项之后,也需要先对其进行初始化,只有初始化后的列表项,才能够被正常地使用。列表项初始化的过程,也是初始化列表项中的
成员变量。 函数原型如下所示:
void vListInitialiseItem(ListItem_t * const pxItem);
| 形参 | 描述 |
| pxItem | 待初始化列表项 |
函数 vListInitialiseItem()无返回值。
函数 vListInitialiseItem()在 list.c 文件中有定义,具体的代码如下所示:
void vListInitialiseItem( ListItem_t * const pxItem )
{/* Make sure the list item is not recorded as being on a list. */pxItem->pxContainer = NULL;/* Write known values into the list item if* configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}
这个函数比较简单,只需将列表项所在列表设置为空,以保证列表项不再任何一个列表项中即可。函数 vListInitialiseItem()初始化后的列表项结构示意图, 如下图所示:

3、函数 vListInsertEnd()
此函数用于将待插入列表的列表项插入到列表 pxIndex 指针指向列表项的前面,是一种无序的插入方法。 函数原型如下所示:
void vListInsertEnd( List_t * const pxList,ListItem_t * const pxNewListItem )
| 形参 | 描述 |
| pxList | 列表 |
| pxNewListItem | 待插入列表项 |
函数 vListInsertEnd()无返回值。
函数 vListInsertEnd()在 list.c 文件中有定义,具体的代码如下所示:
void vListInsertEnd( List_t * const pxList,ListItem_t * const pxNewListItem )
{ListItem_t * const pxIndex = pxList->pxIndex;/* 仅在 configASSERT() 也被定义时有效,这些测试可能会捕获* 链表数据结构在内存中被覆盖的情况。它们不会捕获由* FreeRTOS 配置或使用错误引起的数据错误。 */listTEST_LIST_INTEGRITY( pxList );listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );/* 将一个新的链表项插入到 pxList 中,但不对链表进行排序,* 而是使新链表项成为通过调用 listGET_OWNER_OF_NEXT_ENTRY()* 时最后被移除的项。 */pxNewListItem->pxNext = pxIndex;pxNewListItem->pxPrevious = pxIndex->pxPrevious;/* 仅用于决策覆盖测试。 */mtCOVERAGE_TEST_DELAY();pxIndex->pxPrevious->pxNext = pxNewListItem;pxIndex->pxPrevious = pxNewListItem;/* 记住该项所在的链表。 */pxNewListItem->pxContainer = pxList;( pxList->uxNumberOfItems )++;
}
从上面的代码可以看出,此函数就是将待插入的列表项插入到列表 pxIndex 指向列表项的前面,要注意的时, pxIndex 不一定指向 xListEnd,而是有可能指向列表中任意一个列表项。函
数 vListInsertEnd()插入列表项后的列表结构示意图,如下图所示:

ps:需要注意的时,函数中的end并不是指物理意义上的末尾,事实上插入的位置与pxIndex强相关,最终实现的效果是在pxIndex前面插入列表项!
4、函数 vListInsert()
此函数用于将待插入列表的列表项按照列表项值升序排序的顺序,有序地插入到列表中。函数原型如下所示:
void vListInsert(List_t * const pxList,ListItem_t * const pxNewListItem);
| 形参 | 描述 |
| pxList | 列表 |
| pxNewListItem | 待插入列表项 |
函数 vListInsert()无返回值。
函数 vListInsert()在 list.c 文件中有定义,具体的代码如下所示:
void vListInsert( List_t * const pxList,ListItem_t * const pxNewListItem )
{ListItem_t * pxIterator;const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;/* 仅在 configASSERT() 也被定义时有效,这些测试可能会捕获* 链表数据结构在内存中被覆盖的情况。它们不会捕获由* FreeRTOS 配置或使用错误引起的数据错误。 */listTEST_LIST_INTEGRITY( pxList );listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );/* 将新的链表项插入到链表中,按照 xItemValue 的值进行排序。** 如果链表中已经存在一个具有相同 item value 的链表项,则* 新的链表项应放置在它之后。这确保了存储在就绪列表中的 TCB* (所有 TCB 都具有相同的 xItemValue 值)能够共享 CPU。* 然而,如果 xItemValue 的值与 back marker 相同,则下面的* 迭代循环将不会结束。因此,首先检查该值,并在必要时对算法* 进行轻微修改。 */if( xValueOfInsertion == portMAX_DELAY ){pxIterator = pxList->xListEnd.pxPrevious;}else{/* *** 注意 ************************************************************ 如果你发现应用程序在这里崩溃,可能的原因如下。此外,请参阅* https://www.FreeRTOS.org/FAQHelp.html 获取更多提示,并确保* configASSERT() 已定义!* https://www.FreeRTOS.org/a00110.html#configASSERT** 1) 栈溢出 -* 参见 https://www.FreeRTOS.org/Stacks-and-stack-overflow-checking.html* 2) 中断优先级分配错误,尤其是在 Cortex-M 系列芯片上,* 数值上较高的优先级值表示实际较低的中断优先级,这可能会* 显得反直觉。参见 https://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html* 以及 configMAX_SYSCALL_INTERRUPT_PRIORITY 的定义:* https://www.FreeRTOS.org/a00110.html* 3) 在临界区或调度器挂起时调用 API 函数,或者从中断中调用* 不以 "FromISR" 结尾的 API 函数。* 4) 在使用队列或信号量之前未初始化,或者在调度器启动之前* 使用(是否在调用 vTaskStartScheduler() 之前触发了中断?)。* 5) 如果 FreeRTOS 端口支持中断嵌套,则确保 tick 中断的优先级* 等于或低于 configMAX_SYSCALL_INTERRUPT_PRIORITY。**********************************************************************/for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) /*lint !e826 !e740 !e9087 使用迷你链表结构作为链表末尾以节省 RAM。这是经过检查且有效的。 *//*lint !e440 迭代器移动到不同的值,而不是 xValueOfInsertion。 */{/* 这里不需要做任何事情,只是迭代到所需的插入位置。 */}}pxNewListItem->pxNext = pxIterator->pxNext;pxNewListItem->pxNext->pxPrevious = pxNewListItem;pxNewListItem->pxPrevious = pxIterator;pxIterator->pxNext = pxNewListItem;/* 记住该项所在的链表。这可以加快后续的项移除操作。 */pxNewListItem->pxContainer = pxList;( pxList->uxNumberOfItems )++;
}
从上面的代码可以看出, 此函数在将待插入列表项插入列表之前,会前遍历列表,找到待插入列表项需要插入的位置。待插入列表项需要插入的位置是依照列表中列表项的值按照升序排序确定的。函数 vListInsert()插入列表项后的列表结构示意图,如下图所示:

5、函数 uxListRemove()
此函数用于将列表项从列表项所在列表中移除,函数原型如下所示:
UBaseType_t uxListRemove(ListItem_t * const pxItemToRemove);
| 形参 | 描述 |
| pxItemToRemove | 待移除的列表项 |
函数 uxListRemove()的返回值,如下表所示:
| 返回值 | 描述 |
| 整数 | 待移除列表项移除后,所在列表剩余列表项的数量 |
函数 uxListRemove()在 list.c 文件中有定义,具体的代码如下所示:
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
/* 链表项知道它所在的链表。从链表项中获取链表。 */List_t * const pxList = pxItemToRemove->pxContainer;pxItemToRemove->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->pxContainer = NULL;( pxList->uxNumberOfItems )--;return pxList->uxNumberOfItems;
}
要注意的是函数 uxListRemove()移除后的列表项,依然与列表有着单向联系,即移除后列表项中用于指向上一个和下一个列表项的指针,依然指向列表中的列表项。 函数 uxListRemove()
移除列表项后的列表结构示意图,如下图所示:

操作列表和列表项的宏
在 list.h 文件中定义了大量的宏,用来操作列表以及列表项,如下表所示:
| 宏定义 | 描述 |
| listSET_LIST_ITEM_OWNER( pxListItem, pxOwner ) | 设置列表项的拥有者 |
| listGET_LIST_ITEM_OWNER( pxListItem ) | 获取列表项的拥有者 |
| listSET_LIST_ITEM_VALUE( pxListItem, xValue ) | 设置列表项的值 |
| listGET_LIST_ITEM_VALUE( pxListItem ) | 获取列表项的值 |
| listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxList ) | 获取列表头部列表项的值 |
| listGET_HEAD_ENTRY( pxList ) | 获取列表的头部列表项 |
| listGET_NEXT( pxListItem ) | 获取列表项的下一个列表项 |
| listGET_END_MARKER( pxList ) | 获取列表的尾部列表项 |
| listLIST_IS_EMPTY( pxList ) | 判断列表是否为空 |
| listCURRENT_LIST_LENGTH( pxList ) | 获取列表包含的列表项数量 |
| listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) | 获取下一个列表项的拥有者 |
| listREMOVE_ITEM( pxItemToRemove ) | 将列表项从列表中移除 |
| listINSERT_END( pxList, pxNewListItem ) | 列表末尾插入列表项 |
| listGET_OWNER_OF_HEAD_ENTRY( pxList ) | 获取列表头部列表项的拥有者 |
| listIS_CONTAINED_WITHIN( pxList, pxListItem ) | 判断列表项是否在列表中 |
| listLIST_ITEM_CONTAINER( pxListItem ) | 获取列表项所在列表 |
| listLIST_IS_INITIALISED( pxList ) | 判断列表是否完成初始化 |
这些宏操作列表及列表项的实现都比较简单,读者可阅读 list.h 文件,查看具体的实现方法;也可在后续阅读 FreeRTOS 源码时,遇到这些宏定义时,再进行查阅。
列表项的插入与删除实验
在任意任务中加入以下代码:
void vTaskFunction_1(void *pvParameters)
{List_t TestList;ListItem_t ListItem1;ListItem_t ListItem2;ListItem_t ListItem3;vListInitialise(&TestList); vListInitialiseItem(&ListItem1); vListInitialiseItem(&ListItem2); vListInitialiseItem(&ListItem3); ListItem1.xItemValue=1;ListItem2.xItemValue=3;ListItem3.xItemValue=2; //第二步:打印列表和其他列表项的地址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("\r\n");//第三步:向列表TestList添加列表项ListItem1,并通过串口打印所有//列表项中成员变量pxNext和pxPrevious的值,通过这两个值观察列表//项在列表中的连接情况。vListInsert(&TestList,&ListItem1); //插入列表项ListItem1printf("/******************添加列表项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("\r\n");//第四步:向列表TestList添加列表项ListItem2,并通过串口打印所有//列表项中成员变量pxNext和pxPrevious的值,通过这两个值观察列表//项在列表中的连接情况。vListInsert(&TestList,&ListItem2); //插入列表项ListItem2printf("/******************添加列表项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("\r\n");//第五步:向列表TestList添加列表项ListItem3,并通过串口打印所有//列表项中成员变量pxNext和pxPrevious的值,通过这两个值观察列表//项在列表中的连接情况。vListInsert(&TestList,&ListItem3); //插入列表项ListItem3printf("/******************添加列表项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("\r\n");//第六步:删除ListItem2,并通过串口打印所有列表项中成员变量pxNext和//pxPrevious的值,通过这两个值观察列表项在列表中的连接情况。uxListRemove(&ListItem2); //删除ListItem2printf("/******************删除列表项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("\r\n");//第七步:插入ListItem2,并通过串口打印所有列表项中成员变量pxNext和//pxPrevious的值,通过这两个值观察列表项在列表中的连接情况。TestList.pxIndex=TestList.pxIndex->pxNext; //pxIndex向后移一项,这样pxIndex就会指向ListItem1。vListInsertEnd(&TestList,&ListItem2); //列表末尾添加列表项ListItem2printf("/***************在末尾添加列表项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) {printf("vTaskFunction_1 run!\n");vTaskDelay(200);}
}
测试结果:
/*******************列表和列表项地址*******************/
项目 地址
TestList 0x3fca48a0
TestList->pxIndex 0x3fca48a8
TestList->xListEnd 0x3fca48a8
ListItem1 0x3fca48b4
ListItem2 0x3fca48c8
ListItem3 0x3fca48dc
/************************结束**************************//******************添加列表项ListItem1*****************/
项目 地址
TestList->xListEnd->pxNext 0x3fca48b4
ListItem1->pxNext 0x3fca48a8
/*******************前后向连接分割线********************/
TestList->xListEnd->pxPrevious 0x3fca48b4
ListItem1->pxPrevious 0x3fca48a8
/************************结束**************************//******************添加列表项ListItem2*****************/
项目 地址
TestList->xListEnd->pxNext 0x3fca48b4
ListItem1->pxNext 0x3fca48c8
ListItem2->pxNext 0x3fca48a8
/*******************前后向连接分割线********************/
TestList->xListEnd->pxPrevious 0x3fca48c8
ListItem1->pxPrevious 0x3fca48a8
ListItem2->pxPrevious 0x3fca48b4
/************************结束**************************//******************添加列表项ListItem3*****************/
项目 地址
TestList->xListEnd->pxNext 0x3fca48b4
ListItem1->pxNext 0x3fca48dc
ListItem3->pxNext 0x3fca48c8
ListItem2->pxNext 0x3fca48a8
/*******************前后向连接分割线********************/
TestList->xListEnd->pxPrevious 0x3fca48c8
ListItem1->pxPrevious 0x3fca48a8
ListItem3->pxPrevious 0x3fca48b4
ListItem2->pxPrevious 0x3fca48dc
/************************结束**************************//******************删除列表项ListItem2*****************/
项目 地址
TestList->xListEnd->pxNext 0x3fca48b4
ListItem1->pxNext 0x3fca48dc
ListItem3->pxNext 0x3fca48a8
/*******************前后向连接分割线********************/
TestList->xListEnd->pxPrevious 0x3fca48dc
ListItem1->pxPrevious 0x3fca48a8
ListItem3->pxPrevious 0x3fca48b4
/************************结束**************************//***************在末尾添加列表项ListItem2***************/
项目 地址
TestList->pxIndex 0x3fca48b4
TestList->xListEnd->pxNext 0x3fca48c8
ListItem2->pxNext 0x3fca48b4
ListItem1->pxNext 0x3fca48dc
ListItem3->pxNext 0x3fca48a8
/*******************前后向连接分割线********************/
TestList->xListEnd->pxPrevious 0x3fca48dc
ListItem2->pxPrevious 0x3fca48a8
ListItem1->pxPrevious 0x3fca48c8
ListItem3->pxPrevious 0x3fca48b4
/************************结束**************************/相关文章:
FreeRTOS(3)列表List
在 FreeRTOS 的源码中大量地使用了列表和列表项,因此想要深入学习 FreeRTOS,列表和列表项是必备的基础知识。这里所说的列表和列表项,是 FreeRTOS 源码中 List 和 List Item 的 直译,事实上, FreeRTOS 中的列表和列表项…...
C++和OpenGL实现3D游戏编程【连载23】——几何着色器和法线可视化
欢迎来到zhooyu的C++和OpenGL游戏专栏,专栏连载的所有精彩内容目录详见下边链接: 🔥C++和OpenGL实现3D游戏编程【总览】 1、本节实现的内容 上一节课,我们在Blend软件中导出经纬球模型时,遇到了经纬球法线导致我们在游戏中模型光照显示问题,我们在Blender软件中可以通过…...
Harmony开发笔记(未完成)
一、感想 作为一名拥有11年经验的Android开发者,我亲历了Android从高速发展到如今面临“僧多粥少”的过程。技术的世界瞬息万变,没有一种技术能够让人依赖一辈子。去年初,我自学了鸿蒙系统,并顺利通过了鸿蒙官方的初级和高级认。…...
【Java面试】创建线程有哪几种方式
目录 1.继承Thread类 2.实现Runnable接口 3.实现Callable接口和FutureTask 4.使用Executor框架(线程池) Java并发编程中不同接口和类之间的关系 总结 1.继承Thread类 优点: 简单直观。直接继承Thread类,可以方便地使用Threa…...
在Linux环境下利用MTCNN进行人脸检测(基于ncnn架构)
概述 本文将详细介绍如何在Linux环境下部署MTCNN模型进行人脸检测,并使用NCNN框架进行推理。 1. CMake的安装与配置 下载CMake源码 前往CMake官网下载,找到适合您系统的最新版本tar.gz文件链接,或者直接通过wget下载:CMake官方…...
AI数字人系统源码部署解决方案!!!
一、开场白 如今,科技的步伐越来越快,数字人已经从想象中走进了我们的现实生活。它们在娱乐、教育、医疗等多个领域大放异彩。了解数字人的代码开发技术,能让我们更好地理解其工作原理,为那些想在这一领域大展拳脚或者用数字人技…...
W803|联盛德|WM IoT SDK2.X测试|(1)开箱:开发板及说明
前几天关注的联盛德微电子新推出了WM IoT SDK2.X,正式发布后,邀请用户参加“免费试用,赢千元大礼”活动,填写信息,等待统一发送,很快收到了板子。 活动地址:联盛德微电子WM IoT SDK2.X正式发布…...
003 SpringBoot集成Kafka操作
4.SpringBoot集成Kafka 文章目录 4.SpringBoot集成Kafka1.入门示例2.yml完整配置3.关键配置注释说明1. 生产者优化参数2. 消费者可靠性配置3. 监听器高级特性4. 安全认证配置 4.配置验证方法5.不同场景配置模板场景1:高吞吐日志收集场景2:金融级事务消息…...
2.✨java练习1(熟悉“类”)
1. A B - AcWing题库 问题描述 输入两个整数,求这两个整数的和是多少。 输入格式 输入两个整数A,B,用空格隔开 输出格式 输出一个整数,表示这两个数的和 数据范围 0≤A,B≤1e8 C #include <iostream> // 包含标准输入输出库 using n…...
基本网络安全的实现
基本网络安全的实现 一 :AAA AAA 是Authentication,Authorization and Accounting(认证、授权和计费)的简 称,它提供了一个用来对认证、授权和计费这三种安全功能进行配置的一致性框架, 它是对网络安全…...
快手前端通用静态托管服务KFX演进历程:从崎岖土路到平坦高速
快手静态部署托管服务(KFX)历经四年发展,经历了三个阶段,一步步从勉强能行车的“崎岖土路”到现在多车道并行的“平坦高速”,这一转变极大地提升了资源利用率和效率,满足业务的实际需要。本文将带你了解其背…...
登录逻辑结合redis
1. 用户登录 用户访问登录页面,输入用户名和密码,提交表单。 服务端验证用户名和密码: 如果验证成功,生成 ticket,并将 ticket 和用户 ID 存储在缓存中(如 Redis)。 将 ticket 放入 Cookie 中…...
Locale+Jackson导致Controller接口StackOverflowError异常解决
问题 由于参与的项目有出海需求,即需要给外国人使用,即:需要支持i18n(Internationalization的缩写,共20个字母,除去首尾两个字母,中间有18个,故简称i18n)。 本来是好的…...
安卓工控平板电脑在环境监测设备中的运用
安卓工控平板电脑在环境监测设备中的运用主要体现在以下几个方面: 一、耐用性与可靠性 安卓工控平板电脑通常具有坚固耐用的外壳设计,如铝合金面板和镀锌钢板箱体结构,能够抵抗高温、低温、湿度、震动等恶劣的工作环境。这种耐用性和可靠性…...
【洛谷排序算法】P1012拼数-详细讲解
洛谷 P1012 拼数这道题本身并非单纯考察某种经典排序算法(如冒泡排序、选择排序、插入排序、快速排序、归并排序等)的实现,而是在排序的基础上,自定义了排序的比较规则,属于自定义排序类型的题目。不过它借助了标准库中…...
文心一言AI创意画
介绍 文心一言是百度推出的新一代知识增强大语言模型,属于文心大模型家族的新成员。它能够与人对话互动、回答问题、协助创作,高效便捷地帮助人们获取信息、知识和灵感。 特点 文心一言基于数万亿数据和数千亿知识进行融合学习,采用预训…...
java项目之基于ssm的图书馆书库管理系统(源码+文档)
风定落花生,歌声逐流水,大家好我是风歌,混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的图书馆书库管理系统。项目源码以及部署相关请联系风歌,文末附上联系信息 。 项目简介: 该系统可以实现图书信息管理…...
使用OpenCV实现帧间变化检测:基于轮廓的动态区域标注
在计算机视觉中,帧间差异检测(frame differencing)是一种常用的技术,用于检测视频流中的动态变化区域。这种方法尤其适用于监控、运动分析、目标追踪等场景。在这篇博客中,我们将通过分析一个基于OpenCV的简单帧间差异…...
deepseek从入门到精通-第一篇.本地化部署
前言 自从22年年底开始,人工智能开始从实验室一下子走入了普通人的视野中,chatgpt像一颗石子投入水中,溅起了一波又一波的涟漪。我们都通过各种方式试用大预言模型和机器进行对话或者提问。随着大语言模型的出现,各个类型的大模型…...
2025年SCI一区智能优化算法:真菌生长优化算法(Fungal Growth Optimizer,FGO),提供MATLAB代码
一. 真菌生长优化算法(FGO) 真菌生长优化算法(Fungal Growth Optimizer,FGO)是一种新型的自然启发式元启发式算法,其灵感来源于自然界中真菌的生长行为。该算法通过模拟真菌的菌丝尖端生长、分支和孢子萌发…...
一个行为类似标准库find算法的模板
函数需要两个模板类型参数,一个表示函数的迭代器参数,另一个表示值的类型。 代码 #include<iostream> #include<string> #include<vector> #include<list>using namespace std;template <typename IterType,typename T>…...
Ubutu部署WordPress
前言 什么是word press WordPress是一种使用PHP语言开发的建站系统,用户可以在支持PHP和MySQL数据库的服务器上架设WordPress。它是一个开源的内容管理系统(CMS),允许用户构建动态网站和博客。现在的WordPress已经强大到几乎可以…...
网络渗透作业
第一题:使用Xpath对Order by 语句进行布尔盲注 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns"http://www.w3.org/1999/xhtml&quo…...
BIO、NIO、AIO解析
一、基础概念 1、IO的含义 IO,Input/Output,即输入/输出。从计算机结构来看,IO描述了计算机系统和外部设备之间通讯的过程。从应用程序角度来看,一个进程的地址空间划分为 用户空间(User space) 和 内核空…...
【Python网络爬虫笔记】14-使用代理绕过访问限制
【Python网络爬虫笔记】14-网络代理 目录什么是代理?为什么需要使用代理?代理的类型如何在Python中使用代理?使用requests库设置代理使用urllib库设置代理使用scrapy框架设置代理 典型案例:使用代理爬取豆瓣电影Top250步骤1&#…...
⭐算法OJ⭐位操作实战(C++ 实现)190. Reverse Bits | 268. Missing Number | 338. Counting Bits
文章目录 190. Reverse Bits逐位反转思路步骤代码复杂度分析 268. Missing Number338. Counting Bits动态规划 最低有效位思路步骤代码复杂度分析 动态规划 最后设置位思路步骤代码复杂度分析 190. Reverse Bits Reverse bits of a given 32 bits unsigned integer. 逐位反…...
Linux中Shell运行原理和权限(下)(4)
文章目录 前言一、Shell的运行原理二、Linux当中的权限问题Linux权限的概念如何将普通用户添加到信任列表 三、Linux权限管理文件访问者的分类(人)文件类型和访问权限(事物属性)文件权限值的表示方法文件访问权限的相关设置方法如…...
Java中的缓存技术:Guava Cache vs Caffeine vs Redis
在Java中,缓存技术是提升应用性能的重要手段。常见的缓存技术包括Guava Cache、Caffeine和Redis。它们各有优缺点,适用于不同的场景。以下是对它们的详细对比: 1. Guava Cache 类型: 本地缓存 特点: 基于内存的缓存,适用于单机应…...
C# 弃元的使用
总目录 前言 在C# 7.0及更高版本中,弃元(Discard)是一个新的语言特性,允许开发者在特定情况下忽略某些值。弃元用下划线 _ 作为占位符,明确表示忽略某个值,提升代码可读性 一、弃元是什么? 1.…...
OceanBase数据库实战:Windows Docker部署与DBeaver无缝对接
一、前言 OceanBase 是一款高性能、高可扩展的分布式数据库,适用于大规模数据处理和企业级应用。 随着大数据和云计算的普及,OceanBase 在企业数字化转型中扮演着重要角色。学习 OceanBase 可以帮助开发者掌握先进的分布式数据库技术,提升数…...
