FreeRTOS基础入门——FreeRTOS列表和列表项(八)

 个人名片:

🎓作者简介:嵌入式领域优质创作者
🌐个人主页:妄北y

📞个人QQ:2061314755

💌个人邮箱:[mailto:2061314755@qq.com]
📱个人微信:Vir2025WBY

🖥️个人公众号:科技妄北
🖋️本文为妄北y原创佳作,独家首发于CSDN🎊🎊🎊
💡座右铭:改造世界固然伟大,但改造自我更为可贵。

专栏导航:

妄北y系列专栏导航:

物联网嵌入式开发项目:大学期间的毕业设计,课程设计,大创项目,各种竞赛项目,全面覆盖了需求分析、方案设计、实施与调试、成果展示以及总结反思等关键环节。📚💼💡

QT基础入门学习:对QT的基础图形化页面设计进行了一个简单的学习与认识,利用QT的基础知识进行了翻金币小游戏的制作。🛠️🔧💭

Linux基础编程:初步认识什么是Linux,为什么学Linux,安装环境,进行基础命令的学习,入门级的shell编程。🍻🎉🖥️

深耕Linux应用开发:分享Linux的基本概念、命令行操作、文件系统、用户和权限管理等,网络编程相关知识,TCP/IP 协议、套接字(Socket)编程等,可以实现网络通信功能。常见开源库的二次开发,如libcurl、OpenSSL、json-c、freetype等💐📝💡

Linux驱动开发:Linux驱动开发是Linux系统不可或缺的组成部分,它专注于编写特殊的程序——驱动程序。这些程序承载着硬件设备的详细信息,并扮演着操作系统与硬件间沟通的桥梁角色。驱动开发的核心使命在于确保硬件设备在Linux系统上顺畅运作,同时实现与操作系统的无缝集成,为用户带来流畅稳定的体验。🚀🔧💻

Linux项目开发:Linux基础知识的实践,做项目是最锻炼能力的一个学习方法,这里我们会学习到一些简单基础的项目开发与应用,而且都是毕业设计级别的哦。🤸🌱🚀

非常期待与您一同在这个广阔的互联网天地里,携手探索知识的海洋,互相学习,共同进步。🌐💫🌱 熠熠星光,照亮我们的成长之路

✨✨ 欢迎订阅本专栏,对专栏内容任何问题都可以随时联系博主,共同书写属于我们的精彩篇章!✨✨

文章介绍:

📚本篇文章将深入剖析FreeRTOS学习的精髓与奥秘,与您一同分享相关知识!🎉🎉🎉

若您觉得文章尚可入目,期待您能慷慨地送上点赞、收藏与分享的三连支持!您的每一份鼓励,都是我创作路上源源不断的动力。让我们携手并进,共同奔跑,期待在顶峰相见的那一天,共庆辉煌!🚀🚀🚀

🙏衷心感谢大家的点赞👍、收藏⭐和评论✍️,您的支持是我前进的动力!

目录:

目录

目录:

一、为什么要学列表和列表项:

1.1 列表(List)

1.2 列表项(List Item)

1.3 迷你列表项:

1.4 关键函数和操作

二、列表和列表项初始化

2.1 列表初始化

2.2 列表项初始化:

2.3 列表项插入

2.4 列表项末尾插入

2.5 列表项的删除

2.6 列表的遍历:

三、列表项的插入和删除

3.1 设计目的:

3.2 程序分析:

3.2.1 任务设置:

3.2.2 列表和列表项的定义:

3.2.3 main()函数:

3.2.4 任务函数:

四、程序运行结果分析:

4.1 第一步和第二步

4.2 第三步

4.3 第四步:

4.4 第五步:

4.5 第六步和第七步:


一、为什么要学列表和列表项:

深入理解 FreeRTOS 的源码及其工作原理,有一个关键部分是必不可少的,那就是 FreeRTOS 的列表和列表项。它们是 FreeRTOS 的基本数据结构,广泛用于系统的各个方面,构成了 FreeRTOS 的核心基础。掌握列表和列表项对于理解 FreeRTOS 的内部机制至关重要。

1.1 列表(List)

  • 定义列表是一个有序的集合,用于存储和管理列表项。列表结构为List_t,在文件list.h中有如下定义。
  • 结构:列表通常包含一个头节点(List Head)和一个尾节点(List Tail),以及一些用于管理列表状态的变量,如列表项的数量等。
  • 用途:列表在 FreeRTOS 中用于多种目的,例如任务调度、事件管理、信号量管理等。
typedef struct xLIST {
    listFIRST_LIST_INTEGRITY_CHECK_VALUE  // 校验列表完整性,确保列表未被损坏
    configLIST_VOLATILE UBaseType_t uxNumberOfltems;  // 列表项的数量
    ListItem_t configLIST_VOLATILE *pxIndex;  // 当前列表项目的索引
    MiniListItem_t xListEnd;  // 列表的末尾,表示此列表的变量类型
    listSECOND_LIST_INTEGRITY_CHECK_VALUE  // 第二次校验列表完整性
} List_t;

1.2 列表项(List Item)

  • 定义列表项是列表中的单个元素,可以代表一个任务、一个事件或其他任何需要管理的对象。列表项就是存放在列表中的项目,FreeRTOS提供了两种列表项:列表项和迷你列表项。这两个都在文件1ist.h中有定义。
  • 结构:列表项包含指向前后列表项的指针,以及其他一些用于标识和管理该项的变量。
  • 用途:列表项在 FreeRTOS 中用于表示任务控制块(TCB)、事件等,它们通过列表项在列表中的位置来决定其优先级或其他属性。
好的,以下是将注释翻译为中文后的代码:

```c
struct xLIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*< 如果 configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 被设置为 1,则设置为已知值。 */
	configLIST_VOLATILE TickType_t xItemValue;			/*< 被列出的值。在大多数情况下,这用于按降序排序列表。 */
	struct xLIST_ITEM * configLIST_VOLATILE pxNext;		/*< 指向列表中下一个 ListItem_t 的指针。 */
	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;	/*< 指向列表中上一个 ListItem_t 的指针。 */
	void * pvOwner;										/*< 指向包含该列表项的对象(通常是 TCB)的指针。因此在包含列表项的对象和列表项本身之间存在双向链接。 */
	void * configLIST_VOLATILE pvContainer;				/*< 指向放置该列表项的列表的指针(如果有的话)。 */
	listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*< 如果 configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 被设置为 1,则设置为已知值。 */
};
typedef struct xLIST_ITEM ListItem_t;					/* 出于某种原因,lint 希望这是两个单独的定义。 */
```

这样可以帮助中文读者更容易理解每个字段的用途。
  • listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE 和 listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE 用于完整性检查。
  • xItemValue 是列表项的值。
  • pxNext 指向下一个列表项。
  • pxPrevious 指向前一个列表项,和 pxNext 一起实现双向链表的功能。
  • pvOwner 指向包含该列表项的对象(通常是任务控制块)。
  • pvContainer 指向包含该列表项的列表。

pvContainer与pvOwner区别

pvContainer 用于记录该列表项属于哪个列表。需要注意的是,pvOwnerpvContainer 有不同的含义。在任务控制块(TCB)中,有两个变量 xStateListItemxEventListItem,它们的类型都是 ListItem_t,即它们都是列表项。以 xStateListItem 为例,当创建一个任务后,xStateListItempvOwner 变量会指向该任务的任务控制块,表示 xStateListItem 属于这个任务。当任务进入就绪态时,xStateListItempvContainer 变量会指向就绪列表,表明此列表项位于就绪列表中。

用一个更通俗的例子来说明:假设小王在上二年级,他的父亲是老王。如果把小王比作列表项,那么小王的 pvOwner 属性值就是老王(即小王的所有者),而小王的 pvContainer 属性值就是二年级(即小王所在的位置或集合)。

简而言之:

  • pvOwner 指向拥有该列表项的对象(通常是任务控制块)。
  • pvContainer 指向该列表项所在的列表。

 

1.3 迷你列表项:

迷你列表项在文件list.h中有定义:

// 定义一个小型链表项结构体
typedef struct xMINI_LIST_ITEM {
    // 链表的完整性检查值
    // 用于验证链表结构的完整性
    uint32_t listFIRST; // (1)

    // 列表项的值,类型为 TickType
    TickType_t xItemValue; // (2)

    // 指向下一个链表项的指针
    struct xMINI_LIST_ITEM *pxNext; // (3)

    // 指向前一个链表项的指针
    struct xMINI_LIST_ITEM *pxPrevious; // (4)
} MiniListItem_t;

(1)listFIRST:链表完整性检查值,用于确保链表结构的完整性。
(2)xItemValue:链表项存储的值,类型为 TickType_t。
(3)pxNext:指向链表中下一个项的指针。
(4)pxPrevious:指向链表中前一个项的指针。

迷你列表项与标准列表项相比,只减少了几个成员变量。这使得迷你列表项在功能上与标准列表项相似,但在某些情况下,它提供了一种更轻量的选择。

引入迷你列表项的原因主要是为了在特定场景下减少内存占用。当我们只需要列表项的部分功能时,使用迷你列表项可以避免不必要的内存浪费。例如,在某些数据结构中,如列表结构体 `List_t` 中,最后一个列表项的成员变量 `xListEnd` 可以使用迷你列表项类型。这种方式不仅节省了内存,还提高了系统的效率。

1.4 关键函数和操作

  • 插入和删除FreeRTOS 提供了插入和删除列表项的函数,这些操作确保列表的有序性和一致性。
  • 遍历:可以通过遍历列表来访问其中的每一个列表项,这在任务调度和事件处理中非常常见。
  • 初始化和清理:列表和列表项在使用前需要进行初始化,使用完毕后需要进行清理,以避免内存泄漏或其他问题。

二、列表和列表项初始化

2.1 列表初始化

在创建或定义新的列表时,需要对其进行初始化。列表的初始化实际上是对列表结构体 List_t 中的各个成员变量进行设置。这个初始化过程可以通过调用函数 vListInitialise() 来完成,该函数在 list.c 文件中有具体的实现。

void vListInitialise( List_t * const pxList )
{
	/* 列表结构体包含一个列表项,用于标记列表的结束。为了初始化列表,将列表结束项作为唯一的列表入口插入。 */
	pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );			/*lint !e826 !e740 为了节省内存,迷你列表结构用于作为列表的结束项。这个检查是有效的。 */

	/* 列表结束项的值设置为列表中可能的最大值,以确保它始终位于列表的末尾。 */
	pxList->xListEnd.xItemValue = portMAX_DELAY;

	/* 列表结束项的下一个和上一个指针指向自身,以便我们知道列表是否为空。 */
	pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );	/*lint !e826 !e740 为了节省内存,迷你列表结构用于作为列表的结束项。这个检查是有效的。 */
	pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );/*lint !e826 !e740 为了节省内存,迷你列表结构用于作为列表的结束项。这个检查是有效的。 */

	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 );
}
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );

将列表的索引指针 pxIndex 指向列表结束项 xListEnd,这意味着在初始化时,列表是空的,只有结束项。

pxList->xListEnd.xItemValue = portMAX_DELAY;

将结束项的值设置为 portMAX_DELAY,这是一个足够大的值,确保它在列表中始终位于最后。这种设计有助于在进行列表排序或检索时确保逻辑正确。

pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );

将结束项的 pxNext 和 pxPrevious 指针都指向自身。这意味着在空列表状态下,pxNext 和 pxPrevious 都指向同一项,方便后续的操作判定列表是否为空。

pxList->uxNumberOfItems = ( UBaseType_t ) 0U;

将当前列表项的数量初始化为0,表明列表此时是空的。

listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );

如果定义了数据完整性检查宏 configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES,则写入已知值用于后续的完整性验证。这可以帮助在程序运行期间检测任何意外的内存损坏或错误。

2.2 列表项初始化:

同列表一样,列表项在使用的时候也需要初始化,列表项初始化由函数vListInitialiseItem()
来完成,函数如下:

void vListInitialiseItem( ListItem_t * const pxItem )
{
	/* 确保列表项没有被记录为在列表中。 */
	pxItem->pvContainer = NULL;

	/* 如果 configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 被设置为 1,则为列表项写入已知值。 */
	listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
	listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}

列表项的初始化过程相对简单,仅需将成员变量 pvContainer 设置为 NULL,并为完整性检查的变量赋值。有些朋友可能会疑惑,既然列表项的成员变量比列表要多,为什么初始化函数这么简短?其实,其他成员变量的初始化是根据具体的使用情况进行的。例如,在任务创建函数 xTaskCreate() 中,会对任务控制块中的 xStateListItemxEventListItem 这两个列表项的其他成员变量进行初始化。关于任务创建过程的详细内容,我们将在后面的章节中深入探讨。

2.3 列表项插入

函数 vListInsert() 的目的通常是将一个新的列表项插入到指定的列表中。

void vListInsert(List_t * const pxList, ListItem_t * const pxNewListItem);

参数:

pxList: 指向要插入列表项的目标列表的指针。

pxNewListItem: 指向要插入的新的列表项的指针。

返回值:

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 的顺序排序。
	//
	// 如果列表中已经包含具有相同项值的列表项,则新的列表项应放在其后。
	// 这确保了存储在就绪列表中的任务控制块(TCB)能够共享 CPU。
	// 但是,如果 xItemValue 与后标记相同,则下面的迭代循环将不会结束。
	// 因此,首先检查值,并在必要时稍微修改算法。
	if (xValueOfInsertion == portMAX_DELAY)
	{
		// 如果插入值是最大延迟值,则设置迭代器为列表的最后一个有效节点。
		pxIterator = pxList->xListEnd.pxPrevious;
	}
	else
	{
		/* *** 注意 ***********************************************************
		如果您发现应用程序在此处崩溃,则可能的原因如下。
		此外,请参见 http://www.freertos.org/FAQHelp.html 获取更多提示,并确保定义了 configASSERT()!
		http://www.freertos.org/a00110.html#configASSERT

			1) 堆栈溢出 -
			   请参见 http://www.freertos.org/Stacks-and-stack-overflow-checking.html
			2) 不正确的中断优先级分配,尤其是在 Cortex-M 部件上,数值较高的优先级值表示较低的实际中断优先级,这可能会导致困惑。
			   请参见 http://www.freertos.org/RTOS-Cortex-M3-M4.html 和 configMAX_SYSCALL_INTERRUPT_PRIORITY 的定义
			   http://www.freertos.org/a00110.html
			3) 在临界区内调用 API 函数,或者在调度程序暂停时调用 API 函数,或者从中断中调用未以 "FromISR" 结尾的 API 函数。
			4) 在队列或信号量初始化之前,或在调度程序启动之前使用它们(在 vTaskStartScheduler() 调用之前是否有中断触发?)。
		**********************************************************************/

		// 遍历列表,找到合适的插入位置
		for (pxIterator = (ListItem_t*)&(pxList->xListEnd);
			pxIterator->pxNext->xItemValue <= xValueOfInsertion;
			pxIterator = pxIterator->pxNext) /*lint !e826 !e740 使用迷你列表结构作为列表尾部以节省 RAM。这是经过检查并且有效的。 */
		{
			// 这里没有任何操作,只是迭代到所需的插入位置。
		}
	}

	// 将新的列表项插入到确定的位置
	pxNewListItem->pxNext = pxIterator->pxNext;
	pxNewListItem->pxNext->pxPrevious = pxNewListItem;
	pxNewListItem->pxPrevious = pxIterator;
	pxIterator->pxNext = pxNewListItem;

	// 记住该列表项所在的列表。这将允许后续快速删除该项。
	pxNewListItem->pvContainer = (void*)pxList;

	// 增加列表中的项数
	(pxList->uxNumberOfItems)++;

}

vListInsert 函数的功能是将一个新的列表项插入到一个已排序的双向链表中。

1. 参数定义:

  • List_t * const pxList: 指向要插入的新项的目标链表。
  • ListItem_t * const pxNewListItem: 要插入的新项。

2. 数据完整性检查:

  • 使用 listTEST_LIST_INTEGRITY 和 listTEST_LIST_ITEM_INTEGRITY 函数检查链表和新项的完整性。这在开发中用于捕捉潜在的内存错误。

3. 插入逻辑:

  • 获取要插入项的值 xValueOfInsertion
  • 判断该值是否为 portMAX_DELAY
    • 如果是,设置迭代器 pxIterator 为列表的最后一个有效节点。这是因为在这种情况下,新项应该在最后插入。
  • 如果不是,则进入一个循环,查找合适的插入位置:
    • 从列表尾部开始迭代,查找第一个 xItemValue 大于 xValueOfInsertion 的位置。在找到合适位置之前,迭代器只需向后遍历链表。

4.插入新项:

  • 将新项 pxNewListItem 插入到找到的位置,调整相邻节点的指针以保持链表的双向链接。
  • 设置新项的 pvContainer 指针,以便将来可以轻松找到它所在的列表。

5. 更新列表项计数:

  • 最后,增加链表中的项数 uxNumberOfItems,以反映新项的插入。

6. 插入图解:

(1)在一个空的列表List中插入一个列表值为40的列表项ListItem1,插入完成以后如图所示:

在插入操作完成后,观察一下列表 List 和列表项 ListItem1 中各个成员变量的变化:

  • 列表 List 中的 uxNumberOfItems 变为 1,表明当前列表中包含一个列表项。
  • 列表项 ListItem1 的 pvContainer 成员被设置为 List,这表明该列表项属于列表 List

通过图可以看到,列表的结构呈现为环形,即形成了一个环形链表的布局。

(2)接着再插入一个值为60的列表项ListItem2,插入完成以后如图所示:

a. 比较项值: ListItem2 的值会与 ListItem1 进行比较。如果 ListItem2 的值大于 ListItem1 的值,那么它就会按照顺序被插入在 ListItem1 的后面。

b. 更新指针:

  • ListItem2 的 pxNext 指针会指向 xListEnd,而 pxPrevious 指针会指向 ListItem1
  • ListItem1 的 pxNext 指针会被更新为指向 ListItem2,确保链表的双向链接保持一致。

c. 更新列表状态:

  • 列表 List 的 uxNumberOfItems 再次加一,变为 2,表明此时列表中有两个列表项。

(3)在上面的列表中再插入一个值为50的列表项ListItem3,插入完成以后如图所示:

按照升序排列的方式,ListItem3应该放到ListIteml和ListItem2中间。

2.4 列表项末尾插入

vListInsertEnd 函数的作用是将新列表项 pxNewListItem 添加到指定列表 pxList 的末尾。这意味着不论当前列表中已有多少项,新的项都会被插入到最后一个有效节点之后,确保列表的顺序和结构不受影响。

void vListInsertEnd(List_t * const pxList, ListItem_t * const pxNewListItem);

参数:

pxList:

  • 类型: List_t * const
  • 描述: 指向要插入新列表项的目标列表。这个列表应该已经被初始化并准备好接收新的列表项。

pxNewListItem:

  • 类型: ListItem_t * const
  • 描述: 指向要插入的列表项。这是一个新的节点,它将被添加到 pxList 的末尾。

返回值:

void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
    // 获取当前列表的索引指针,通常指向列表的结束节点(xListEnd)
    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->pvContainer = ( void * ) pxList; 

    // 列表中的项数加一
    ( pxList->uxNumberOfItems )++;
}

vListInsertEnd 函数的主要目的是将一个新的列表项 pxNewListItem 插入到指定的列表 pxList 的末尾。这是一个双向链表的操作,操作完成后,新的列表项将成为链表中的最后一项。

1. 获取索引指针:pxIndex 通常指向链表的结束节点(如 xListEnd),此节点不包含有效数据,只作为链表的终止标记。

ListItem_t * const pxIndex = pxList->pxIndex;

2. 完整性检查:这两个宏用于检查列表及新列表项的完整性。这些检查有助于捕获潜在的内存损坏或不一致性,确保在插入操作前数据结构是有效的。

listTEST_LIST_INTEGRITY( pxList );
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );

3. 设置新项的指针:设置新项的 pxNext 指针指向当前索引(结束节点),并将其 pxPrevious 指针指向当前索引的前一个项。这一步是插入操作的关键,确保新项能够正确链接到链表。

pxNewListItem->pxNext = pxIndex;
pxNewListItem->pxPrevious = pxIndex->pxPrevious;

4. 覆盖测试延迟:该行代码用于测试覆盖率,通常在单元测试中使用。它可以帮助开发人员确保代码的不同路径得到测试。

mtCOVERAGE_TEST_DELAY();

5. 更新指针:这里,首先更新 pxIndex(结束节点)前一个项的 pxNext 指针,使其指向新插入的项。然后,更新 pxIndex 的 pxPrevious 指针,使其指向新的列表项。这样,链表的双向链接得以保持。

pxIndex->pxPrevious->pxNext = pxNewListItem;
pxIndex->pxPrevious = pxNewListItem;

6. 记录列表项所属的列表:此行代码存储新插入项所属的列表,使得该项可以在后续操作中快速访问其所属列表。

pxNewListItem->pvContainer = ( void * ) pxList;

7. 更新项计数:最后,增加列表中的项数,以反映新项的插入。这是确保列表状态一致的重要步骤。

( pxList->uxNumberOfItems )++;

8.插入图示:

(1)在插入列表项之前我们先准备一个默认列表,如图所示:

(2)在上面的列表中插入一个值为50的列表项ListItem3,插入完成以后如图:

列表List的pxIndex指向列表项ListIteml,因此调用函数VListInsertEnd()插入ListItem3的话就会在ListIteml的前面插入。

2.5 列表项的删除

有列表项的插入,那么必然有列表项的删除,列表项的删除通过函数uxListRemove()来完成,函数原型如下:

UBaseType_t uxListRemove(ListItem_t * const pxItemToRemove);

参数:

pxItemToRemove:

  • 类型: ListItem_t * const
  • 描述: 指向要删除的列表项。调用者应确保该项是有效的并且已存在于列表中。

返回值:

  • 返回类型: UBaseType_t
  • 描述: 函数返回删除列表项后列表中剩余的项数。这使得调用者能够了解列表的当前状态,并在需要时进行相应的操作。
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
    /* 列表项知道自己在哪个列表中。通过列表项获取该列表。 */
    List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;

    // 更新后继项的前驱指针,使其指向被删除项的前驱项
    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->pvContainer = NULL;

    // 减少列表项计数
    ( pxList->uxNumberOfItems )--;

    // 返回剩余的项数
    return pxList->uxNumberOfItems;
}

1. 获取列表:通过 pxItemToRemove 的 pvContainer 成员获取当前项所在的链表。这是一个重要的步骤,因为后续的操作需要知道要修改哪个列表。

List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;

2. 更新指针:

这两行代码实现了链表的重链接:

        将被删除项的前驱项的 pxNext 指针指向被删除项的后续项。

        将被删除项的后续项的 pxPrevious 指针指向被删除项的前驱项。

通过这两步,成功地将被删除项从链表中移除,同时保持链表的完整性。

pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;

3. 覆盖测试延迟:

mtCOVERAGE_TEST_DELAY();

4. 更新索引:

这里检查当前列表的索引是否指向被删除项。如果是,更新索引为指向被删除项的前驱项。这确保了即使删除了索引项,索引也始终指向有效的列表项。

mtCOVERAGE_TEST_MARKER() 是用来标记不同的代码路径,以便在测试中进行覆盖率分析。

if( pxList->pxIndex == pxItemToRemove )
{
    pxList->pxIndex = pxItemToRemove->pxPrevious;
}
else
{
    mtCOVERAGE_TEST_MARKER();
}

5. 清空容器指针:

将被删除项的容器指针设置为 NULL,以防止后续意外访问或错误操作。这是一个良好的防护措施,确保被删除项不会被误用。

pxItemToRemove->pvContainer = NULL;

6减少项计数:更新链表中项的数量,表示已成功删除一个列表项。这是确保链表状态一致的重要步骤。

( pxList->uxNumberOfItems )--;

7. 返回值:返回当前链表中剩余的项数。这对调用者来说是有用的信息,能够了解链表的当前状态。

return pxList->uxNumberOfItems;

2.6 列表的遍历:

在介绍 List_t 结构体时提到,其中的成员变量 pxIndex 用于遍历列表。FreeRTOS 提供了一个用于遍历列表的宏,称为 listGET_OWNER_OF_NEXT_ENTRY每次调用这个宏时,pxIndex 变量将指向列表中的下一个项,并返回该项的 pxOwner 变量的值。这一宏的具体定义位于 list.h 文件中。

这个宏使得遍历操作变得简单高效,能够在循环中逐个访问列表中的每个元素,便于执行相应的处理。

/*
 * 访问函数,用于获取列表中下一个条目的所有者。
 *
 * 列表成员 pxIndex 用于遍历列表。调用 listGET_OWNER_OF_NEXT_ENTRY 会将
 * pxIndex 增加到列表中的下一个项,并返回该条目的 pxOwner 参数。
 * 因此,通过多次调用此函数,可以遍历列表中的每个项。
 *
 * 列表项的 pxOwner 参数是指向拥有该列表项的对象的指针。
 * 在调度器中,这通常是一个任务控制块(TCB)。
 * pxOwner 参数有效地在列表项和其所有者之间创建了一个双向链接。
 *
 * @param pxTCB pxTCB 被设置为下一个列表项所有者的地址。
 * @param pxList 要从中返回下一个项所有者的列表。
 *
 * \page listGET_OWNER_OF_NEXT_ENTRY listGET_OWNER_OF_NEXT_ENTRY
 * \ingroup LinkedList
 */
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )										\
{																							\
    List_t * const pxConstList = ( pxList );												\
    /* 将索引增加到下一个项并返回该项,确保不会返回列表末尾的标记。 */		\
    ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;							\
    /* 检查当前索引是否指向列表末尾的标记,如果是则继续前进到下一个项。 */	\
    if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )	\
    {																						\
        ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;						\
    }																						\
    /* 将下一个项的所有者指针赋值给 pxTCB。 */										\
    ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;											\
}

1. 初始化常量指针:将传入的列表指针 pxList 赋值给一个常量指针 pxConstList,以避免意外修改。

List_t * const pxConstList = ( pxList );

2. 增加索引:将 pxIndex 移动到下一个列表项。

( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;

3. 检查末尾标记:检查当前索引是否指向列表的末尾标记 xListEnd,如果是,则继续移动到下一个项,以跳过末尾标记。

if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )

4.获取所有者:将当前项的 pvOwner 值赋给 pxTCB,即获取到下一个项的所有者。

( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;

该宏 listGET_OWNER_OF_NEXT_ENTRY 提供了一种简便的方法来遍历 FreeRTOS 中的链表。通过递增 pxIndex,用户可以轻松获取到列表中的每个项的所有者,同时确保不会访问到列表的末尾。

三、列表项的插入和删除

3.1 设计目的:

学习如何使用 FreeRTOS 的列表和列表项操作函数,并通过实践验证理论分析的正确性。

  • start task:

    • 负责创建其他两个任务(task1task 和 task2task)。
    • 启动时输出创建任务的信息。
  • task1task:

    • 控制 LED0(假设为硬件上 LED 的标识)闪烁,以指示系统正在运行。
    • 可设定一定的延迟,使 LED 闪烁周期可见。
  • task2task:

    • 用于执行列表和列表项的操作。
    • 调用 FreeRTOS 提供的列表相关 API 函数,并通过串口输出信息,帮助观察这些 API 的运行过程。

3.2 程序分析:

3.2.1 任务设置:

任务优先级、堆栈大小和任务句柄等设置如下:

//任务优先级
#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.2.2 列表和列表项的定义:

//定义一个测试用的列表和3个列表项
List_t TestList;		//测试用列表
ListItem_t ListItem1;	//测试用列表项1
ListItem_t ListItem2;	//测试用列表项2
ListItem_t ListItem3;	//测试用列表项3

3.2.3 main()函数:

int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4	 
	delay_init();	    				//延时函数初始化	 
	uart_init(115200);					//初始化串口
	LED_Init();		  					//初始化LED
	KEY_Init();							//初始化按键
	LCD_Init();							//初始化LCD
	
    POINT_COLOR = RED;
	LCD_ShowString(30,10,200,16,16,"ATK STM32F103/407");	
	LCD_ShowString(30,30,200,16,16,"FreeRTOS Examp 7-1");
	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,"2016/11/25");
	
	//创建开始任务
    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();          //开启任务调度
}

3.2.4 任务函数:

任务函数start_task()和task1_task()都比较简单,这里为了缩减篇幅就不列出来了,重点看一下任务函数list_task(),函数如下:

//开始任务任务函数
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");
	while(KEY_Scan(0)!=WKUP_PRES) 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)!=WKUP_PRES) 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)!=WKUP_PRES) 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)!=WKUP_PRES) delay_ms(10);					//等待KEY_UP键按下
	
	//第六步:删除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("按下KEY_UP键继续!\r\n\r\n\r\n");
	while(KEY_Scan(0)!=WKUP_PRES) 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个时钟节拍	
	}
}

任务函数 list_task() 旨在通过调用与 FreeRTOS 列表和列表项相关的 API 函数,对列表及其项执行各种操作。每次调用 API 函数后,这个任务都会通过串口输出当前列表的状态和项之间的连接信息。

这种输出将使我们能够直观地观察在插入、删除或在列表末尾添加项时,列表结构的变化情况。通过这些信息,我们可以清晰地理解和分析列表操作的结果和影响。

1. start_task()

功能:

  • 负责创建 task1_task 和 list_task,并在创建后删除自身。

代码分析:

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();            // 退出临界区
}
  • 临界区: 使用 taskENTER_CRITICAL() 和 taskEXIT_CRITICAL() 确保在创建任务时不会被其他任务打断,确保任务创建的原子性。
  • 任务创建: 使用 xTaskCreate 创建两个任务,分别是 task1_task(LED 控制任务)和 list_task(列表操作任务)。
  • 任务删除vTaskDelete(StartTask_Handler) 删除 start_task 自身,因为它的主要功能是创建其他任务。

2. task1_task()

功能

  • 负责控制 LED0 的闪烁。

代码分析

void task1_task(void *pvParameters)
{
	while(1)
	{
		LED0=!LED0;                       // 切换 LED0 状态
        vTaskDelay(500);                  // 延时500ms	
	}
}
  • LED 控制: 每 500 毫秒切换一次 LED0 的状态,表现为 LED 的闪烁。
  • 任务延时vTaskDelay(500) 将任务延时 500ms,意味着该任务不会在这段时间内执行其他操作,允许其他任务运行。

3. list_task()

功能

  • 实现了对 FreeRTOS 列表的管理,包含列表的初始化、插入、删除等操作,同时通过串口输出列表的状态。

代码分析

(1)列表和项的初始化:初始化列表和列表项,准备进行后续操作。

vListInitialise(&TestList);
vListInitialiseItem(&ListItem1);
vListInitialiseItem(&ListItem2);
vListInitialiseItem(&ListItem3);

(2)设置列表项的值

ListItem1.xItemValue=40; // 设置列表项值
ListItem2.xItemValue=60;
ListItem3.xItemValue=50;

(3)打印列表和项的地址

printf("/*******************列表和列表项地址*******************/\r\n");
// 打印地址

(4)插入列表项并打印状态:每次插入后,调用 vListInsert() 函数,并打印当前列表的状态,包括 pxNext 和 pxPrevious 指针,帮助观察列表的连接情况。

vListInsert(&TestList,&ListItem1);
printf("/******************添加列表项ListItem1*****************/\r\n");

(5)删除列表项:使用 uxListRemove() 删除 ListItem2,并在删除后打印列表状态,以确认连接关系的改变。

uxListRemove(&ListItem2);

(6)末尾插入:使用 vListInsertEnd() 将 ListItem2 插入到列表末尾,并打印状态以观察变化。

vListInsertEnd(&TestList,&ListItem2);

(7)LED控制:在列表操作完成后,控制 LED1 的闪烁,作为任务的结束部分。

任务函数 list_task() 的主要功能是利用与列表和列表项相关的 API 函数对列表进行各种操作,同时通过串口输出每次调用 API 函数后列表和列表项的连接信息。通过这些输出,我们可以直观地观察到在执行插入、删除和在列表末尾添加项时,列表的结构变化情况。这样,可以帮助我们更好地理解和分析列表操作的影响。

四、程序运行结果分析:

4.1 第一步和第二步

第一步和第二步的主要目的是初始化列表及其项,并通过串口输出列表和列表项的地址。这一过程是开发板复位后默认执行的,以下是串口调试助手所显示的信息:

由于这些列表和列表项的地址的前六位都为 0x200000,只有最低两位不同,因此我们可以用这最低两位来表示它们的地址。然而,请注意,不同的硬件平台或编译环境下,列表和列表项的地址可能会有所不同,建议以实际实验结果为准。

根据图的分析,我们可以总结出以下信息:

列表 TestList 的地址为 b4。

列表项 ListItem1、ListItem2 和 ListItem3 的地址分别为 c8、dc 和 f0。

列表 TestList 的 xListEnd 地址为 bc。

TestList 的 pxIndex 指向地址 bc,而这个地址正是迷你列表项 xListEnd 的地址。这一观察与我们在分析列表初始化函数 vListInitialise() 时得到的结果相符。

4.2 第三步

按一下KEY_UP键,执行第三步,第三步是向列表TestList中插入列表项ListIteml,列表项ListIteml的成员变量xItem Value的值为40。第三步执行完以后串口调试助手输出如图所示:

1. xListEnd 的 pxNext 指向地址 c8,而 c8 是 ListItem1 的地址,这表明 xListEnd 的 pxNext 指向 ListItem1。
2. 列表项 ListItem1 的 pxNext 指向地址 bc,而 bc 是 xListEnd 的地址,说明 ListItem1 的 pxNext 指向 xListEnd。
3. xListEnd的pxPrevious指向地址c8,而c8是 ListItem1 的地址,这意味着xListEnd 的 pxPrevious 指向 ListItem1。
4. ListItem1的 pxPrevious 指向地址 bc,而bc是xListEnd的地址,说明 ListItem1 的 pxPrevious 指向 xListEnd。

4.3 第四步:

按一下KEY_UP键,执行第四步,第四步是向列表TestList中插入列表项ListItem2,列表
项ListItem2的成员变量xItem Value的值为60。第四步执行完以后串口调试助手输出如图所示:

1. xListEnd 的 pxNext 指向 ListItem1。

2. ListItem1 的 pxNext 指向 ListItem2。

3. ListItem2 的 pxNext 指向 xListEnd。

对于列表项的 pxPrevious,分析过程与上述类似,因此在后续步骤中,我们将只关注 pxNext 成员变量。可以用简易示意图表示这一关系,如图所示:

4.4 第五步:

按下 KEY UP 键,执行第五步,即将列表项 ListItem3 插入到 TestList 中。此时,`ListItem3` 的成员变量 xItemValue 值为 50。在完成第四步后,串口调试助手会输出如图 7.7.2.6 所示的信息。

分析图可以得出一下信息:

1. xListEnd的pxNext指向ListIteml。

2. ListItem1的pxNext指向ListItem3。

3. ListItem3的pxNext指向ListItem2。

4. ListItem2的pxNext指向xListEnd。

通过这几步可以看出列表项的插入是根据xItem Value的值做升序排列的,这个和我们分析函数vListInsert()得到的结果一样,说明我们的分析是对的!

4.5 第六步和第七步:

这两步是观察函数uxListRemove()和vListInsertEnd()的运行过程的,分析过程和前五步一
样。这里就不做分析了,大家自行根据串口调试助手输出的信息做分析。

📝大佬觉得本文有所裨益,不妨轻点一下👍给予鼓励吧!

❤️❤️❤️本人虽努力,但能力尚浅,若有不足之处,恳请各位大佬不吝赐教,您的批评指正将是我进步的动力!😊😊😊

💖💖💖若您认为此篇文章对您有所帮助,烦请点赞👍并收藏🌟,您的支持是我前行的最大动力!

🚀🚀🚀任务在默默中完成,价值在悄然间提升。让我们携手共进,一起加油,迎接更美好的未来!🌈🌈🌈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

普通 网友

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值