FreeRTOS(3)列表List

news/2025/2/27 11:43:29

在 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 ) );
    }
    #endif

    pxList->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);		//插入列表项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("\r\n");

	//第四步:向列表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("\r\n");

	//第五步:向列表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("\r\n");

	//第六步:删除ListItem2,并通过串口打印所有列表项中成员变量pxNext和
	//pxPrevious的值,通过这两个值观察列表项在列表中的连接情况。
	uxListRemove(&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("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);				//列表末尾添加列表项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) {
        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
/************************结束**************************/

http://www.niftyadmin.cn/n/5870061.html

相关文章

SSD网络预测与训练阶段总结

SSD网络预测与训练阶段详解 一、预测阶段&#xff08;Inference&#xff09; 特征提取 输入图像通过卷积神经网络&#xff08;如VGG-16&#xff09;提取多尺度特征图。特征图尺寸逐层减小&#xff08;例如&#xff1a;384384 → 1919&#xff09;&#xff0c;浅层保留高分辨率…

【Linux知识】Linux上从源码编译到软件安装全过程详细说明

文章目录 **1. 下载源码****(1) 使用 wget 或 curl 下载****(2) 解压源码** **2. 配置编译环境****(1) 执行 ./configure 脚本**常见参数说明&#xff1a; **3. 编译源码****(1) 执行 make** **4. 安装软件****(1) 执行 make install****(2) 自定义安装路径** **5. 验证安装***…

【红队利器】单文件一键结束火绒6.0

关于我们 4SecNet 团队专注于网络安全攻防研究&#xff0c;目前团队成员分布在国内多家顶级安全厂商的核心部门&#xff0c;包括安全研究领域、攻防实验室等&#xff0c;汇聚了行业内的顶尖技术力量。团队在病毒木马逆向分析、APT 追踪、破解技术、漏洞分析、红队工具开发等多个…

Linux Kernel Connection Tracking Table

在 Linux 内核中&#xff0c;连接跟踪表&#xff08;Connection Tracking Table&#xff0c;简称 conntrack&#xff09;是一个用于跟踪网络连接状态的核心组件。它主要由 Netfilter 框架管理&#xff0c;广泛应用于防火墙、NAT&#xff08;网络地址转换&#xff09;和负载均衡…

计算机工具基础(五)——Vim

Vim 本系列博客为MIT《Missing in CS Class(2020)》课程笔记 Vim是终端环境中常用的纯文本编辑器。 模式 Vim有如下5种模式&#xff1a; 常规模式(Normal)&#xff1a;进入Vim后的默认模式&#xff0c;用于阅读文件。以Esc自其他模式中退至此模式插入模式(Insert)&#xff1…

探索AI新前沿,CoT推理赋能文生图!港中文首次提出文生图的o1推理和inference scaling新范式

OpenAI的o1模型凭借思维链&#xff08;Chain-of-Thought, CoT&#xff09;技术&#xff0c;在推理能力上实现了质的飞跃&#xff0c;引领了大模型理解领域的新风尚。然而&#xff0c;这一创新的火花能否照亮图像生成领域&#xff1f;近日&#xff0c;来自香港中文大学、北京大学…

使用消息队列怎样防止消息重复?

大家好&#xff0c;我是君哥。 使用消息队列时&#xff0c;我们经常会遇到一个可能对业务产生影响的问题&#xff0c;消息重复。在订单、扣款、对账等对幂等有要求的场景&#xff0c;消息重复的问题必须解决。 那怎样应对重复消息呢&#xff1f;今天来聊一聊这个话题。 1.三…

基于阿里云PAI平台快速部署DeepSeek大模型实战指南

一、DeepSeek大模型&#xff1a;企业级AI应用的新标杆 1.1 为什么选择DeepSeek&#xff1f; 近期&#xff0c;DeepSeek系列模型凭借其接近GPT-4的性能和开源策略&#xff0c;成为全球开发者关注的焦点。在多项国际评测中&#xff0c;DeepSeek-R1模型在推理能力、多语言支持和…