一文带你深度了解FreeRTOS列表和列表项

本文详细介绍了FreeRTOS中的列表和列表项,包括它们的概念、初始化、插入、删除和遍历方法,以及它们在任务管理中的作用,为理解FreeRTOS源码提供基础支持。
摘要由CSDN通过智能技术生成

 前言

本篇文章记录我学习FreeRTOS的过程中,关于FreeRTOS中列表和列表项的学习。希望我的分享能给你带来不一样的收获!

目录

​编辑

 前言

一、引言

二、 FreeRTOS列表和列表项

(一)、FreeRTOS列表和列表项简介

1、列表

2、列表项

3.迷你列表项

(二)、列表和列表项初始化 

1、列表初始化

2、列表项初始化 

(三) 、列表项插入

1、列表项插入的相关函数

​编辑

2、列表项插入过程图解

(四)、列表项末尾插入 

1、列表项末尾插入函数分析

2、列表项末尾插入图解 

(五)、列表项的删除 

​(六)、列表的遍历

三、结语


 一、引言

要想看懂FreeRTOS源码并学习其原理,有一个东西绝对跑不了,那就是FreeRTOS 的列表和列表项。列表和列表项是FreeRTOS的一个数据结构,FreeRTOS大量使用到了列表和列表项,它是FreeRTOS 的基石。要想深入学习并理解FreeRTOS,那么列表和列表项就必须首先掌握,否则后面根本就没法进行。本篇文章分为如下几部分:
1、什么是列表和列表项
2、列表和列表项的初始化7.3列表项的插入
3、列表项末尾插入

4、列表项的删除

5、列表项的遍历

二、 FreeRTOS列表和列表项

(一)、FreeRTOS列表和列表项简介

1、列表

列表是FreeRTOS中的一个数据结构,概念上和链表有点类似,列表被用来跟踪FreeRTOS中的任务。与列表相关的全部东西都在文件 list.c和 list.h中。在list.h中定义了一个叫List_t的结构体,如下:

(1)和(5)这两个都是用来检查列表完整性的﹐需要将宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,开启以后会向这两个地方分别添加一个变量xListIntegrityValue1和xListIntegrityValue2,在初始化列表的时候会这两个变量中写入一个特殊的值,默认不开启这个功能。
(2)、uxNumberOfltems用来记录列表中列表项的数量。
(3)、pxIndex用来记录当前列表项索引号,用于遍历列表。

(4)、列表中最后一个列表项,用来表示列表结束,此变量类型为MiniListltem_t,这是一个迷你列表项。

列表结构示意图如下:

图中并未列出用于列表完整性检查的成员变量。

2、列表项

列表项就是存放在列表中的项目,FreeRTOS提供了两种列表项:列表项和迷你列表项。这两个都在文件list.h中有定义,先来看一下列表项,定义如下:

(1)和(7)、用法和列表一样,用来检查列表项完整性的。以后我们在学习列表项的时候不讨论这个功能!
(2)、xltemValue为列表项值。

(3)、pxNext 指向下一个列表项。
(4)、pxPrevious指向前一个列表项,和 pxNext配合起来实现类似双向链表的功能。

(5)、pvOwner记录此链表项归谁拥有,通常是任务控制块。
(6)、pvContainer用来记录此列表项归哪个列表

注意和pvOwner 的区别,在前面介绍任务控制块TCB_t 的时候说了在TCB_t中有两个变量xStateListItem和xEventListItem,这两个变量的类型就是ListItem_t,也就是说这两个成员变量都是列表项。

以xStateListItem为例,当创建一个任务以后xStateListItem的pvOwner变量就指向这个任务的任务控制块,表示 xSateListItem属于此任务。当任务就绪态以后xStateListItem的变量pvContainer 就指向就绪列表,表明此列表项在就绪列表中。举个通俗一点的例子:小王在上二年级,他的父亲是老王。如果把小王比作列表项,那么小王的pvOwner属性值就是老王,小王的pvContainer属性值就是二年级。

列表结构示意图如下(图中并未列出用于列表项完整性检查的成员变量!):

3.迷你列表项

上面我们我们说了列表项,现在来看一下迷你列表项,迷你列表项在文件 list.h 中有定义,如下:

(1)用于检查迷你列表项的完整性;

(2)xItemValue记录列表列表项值;

(3)pxNext指向下一个列表项;

(4)pxPrevious指向上一个列表项;

可以看出迷你列表项只是比列表项少了几个成员变量,迷你列表项有的成员变量列表项都有的,没感觉有什么本质区别啊?那为什么要弄个迷你列表项出来呢?

那是因为有些情况下我们不需要列表项这么全的功能,可能只需要其中的某几个成员变量,如果此时用列表项的话会造成内存浪费!比如上面列表结构体List_t中表示最后一个列表项的成员变量xListEnd 就是MiniListItem_t类型的。
 

(二)、列表和列表项初始化 

1、列表初始化

新创建或者定义的列表需要对其做初始化处理,列表的初始化其实就是初始化列表结构体List_t中的各个成员变量,列表的初始化通过使函数 vListInitialise()来完成,此函数在list.c中有定义,函数如下:

(1)、xListEnd用来表示列表的末尾,而pxIndex表示列表项的索引号,此时列表只有一个列表项,那就是xListEnd,所以pxIndex指向xListEnd。
(2)、xListEnd 的列表项值初始化为portMAX_DELAY,portMAX_DELAY是个宏,在文件portmacro.h中有定义。根据所使用的MCU的不同, portMAX_DELAY值也不相同,可以为0xfmf或者OxffffffffUL,本教程中为0xffmmmUL。
(3)、初始化列表项xListEnd 的 pxNext变量,因为此时列表只有一个列表项xListEnd,因此pxNext只能指向自身。

(4)、同(3)一样,初始化xListEnd的 pxPrevious变量,指向xListEnd自身。
(5)、由于此时没有其他的列表项,因此uxNumberOfItems为0,注意,这里没有算xListEnd。(6)和(7)、初始化列表项中用于完整性检查字段,只有宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES为1的时候才有效。同样的根据所选的MCU不同其写入的值也不同,可以为Ox5a5a或者Ox5a5a5a5aUL。STM32是32位系统写入的是Ox5a5a5a5aUL,列表初始化完以后如图所示:

2、列表项初始化 

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

列表项的初始化很简单,只是将列表项成员变量 pvContainer初始化为NULL,并且给用于完整性检查的变量赋值。有朋友可能会问,列表项的成员变量比列表要多,怎么初始化函数就这么短﹖其他的成员变量什么时候初始化呢?这是因为列表项要根据实际使用情况来初始化,比如任务创建函数xTaskCreate()就会对任务堆栈中的xStateListItem和 xEventListItem这两个列表项中的其他成员变量在做初始化。

(三) 、列表项插入

1、列表项插入的相关函数

列表项的插入操作通过函数vListInsert()来完成,函数原型如下:

void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;

	/* Only effective when configASSERT() is also defined, these tests may catch
	the list data structures being overwritten in memory.  They will not catch
	data errors caused by incorrect configuration or use of FreeRTOS. */
	listTEST_LIST_INTEGRITY( pxList );
	listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );

	/* Insert the new list item into the list, sorted in xItemValue order.

	If the list already contains a list item with the same item value then the
	new list item should be placed after it.  This ensures that TCB's which are
	stored in ready lists (all of which have the same xItemValue value) get a
	share of the CPU.  However, if the xItemValue is the same as the back marker
	the iteration loop below will not end.  Therefore the value is checked
	first, and the algorithm slightly modified if necessary. */
	if( xValueOfInsertion == portMAX_DELAY )
	{
		pxIterator = pxList->xListEnd.pxPrevious;
	}
	else
	{
		/* *** NOTE ***********************************************************
		If you find your application is crashing here then likely causes are
		listed below.  In addition see http://www.freertos.org/FAQHelp.html for
		more tips, and ensure configASSERT() is defined!
		http://www.freertos.org/a00110.html#configASSERT

			1) Stack overflow -
			   see http://www.freertos.org/Stacks-and-stack-overflow-checking.html
			2) Incorrect interrupt priority assignment, especially on Cortex-M
			   parts where numerically high priority values denote low actual
			   interrupt priorities, which can seem counter intuitive.  See
			   http://www.freertos.org/RTOS-Cortex-M3-M4.html and the definition
			   of configMAX_SYSCALL_INTERRUPT_PRIORITY on
			   http://www.freertos.org/a00110.html
			3) Calling an API function from within a critical section or when
			   the scheduler is suspended, or calling an API function that does
			   not end in "FromISR" from an interrupt.
			4) Using a queue or semaphore before it has been initialised or
			   before the scheduler has been started (are interrupts firing
			   before vTaskStartScheduler() has been called?).
		**********************************************************************/

		for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) /*lint !e826 !e740 The mini list structure is used as the list end to save RAM.  This is checked and valid. */
		{
			/* There is nothing to do here, just iterating to the wanted
			insertion position. */
		}
	}

	pxNewListItem->pxNext = pxIterator->pxNext;
	pxNewListItem->pxNext->pxPrevious = pxNewListItem;
	pxNewListItem->pxPrevious = pxIterator;
	pxIterator->pxNext = pxNewListItem;

	/* Remember which list the item is in.  This allows fast removal of the
	item later. */
	pxNewListItem->pvContainer = ( void * ) pxList;

	( pxList->uxNumberOfItems )++;
}
/*-----------------------------------------------------------*/

参数:

函数 vListInsert()的参数pxList 决定了列表项要插入到哪个列表中,pxNewListItem决定了要插入的列表项,但是这个列表项具体插入到什么地方呢?要插入的位置由列表项中成员变量xltemValue来决定。列表项的插入根据xltemValue的值按照升序的方式排列!接下来我们来具体看一下函数vListInsert()的整个运行过程,函数代码如下:

(1)、获取要插入的列表项值,即列表项成员变量xltemValue的值,因为要根据这个值来确定列表项要插入的位置。
(2)、这一行和下一行代码用来检查列表和列表项的完整性的。其实就是检查列表和列表项中用于完整性检查的变量值是否被改变。这些变量的值在列表和列表项初始化的时候就被写入了,这两行代码需要实现函数configASSERT()!
(3)、要插入列表项,第一步就是要获取该列表项要插入到什么位置!如果要插入的列表项的值等于portMAX_DELAY,也就是说列表项值为最大值,这种情况最好办了,要插入的位置就是列表最末尾了。
(4)、
获取要插入点,注意!列表中的xListEnd 用来表示列表末尾,在初始化列表的时候xListEnd的列表值也是portMAX_DELAY,此时要插入的列表项的列表值也是portMAX_DELAY。这两个的顺序该怎么放啊?通过这行代码可以看出要插入的列表项会被放到xListEnd前面。
(5)、要插入的列表项的值如果不等于portMAX_DELAY那么就需要在列表中一个一个的找自己的位置,这个for 循环就是找位置的过程,当找到合适列表项的位置的时候就会跳出。由于这个for循环是用来寻找列表项插入点的,所以for 循环体里面没有任何东西。这个查找过程是按照升序的方式查找列表项插入点的。
(6)、经过上面的查找,我们已经找到列表项的插入点了,从本行开始接下来的四行代码就是将列表项插入到列表中,插入过程和数据结构中双向链表的插入类似。像FreeRTOS这种RTOS系统和一些协议栈都会大量用到数据结构的知识,所以建议大家没事的时候多看看数据结构方面的书籍,否则的话看源码会很吃力的。
(7)、列表项已经插入到列表中了,那么列表项的成员变量 pvContainer 也该记录此列表项属于哪个列表的了。
(8)、列表的成员变量uxNumberOfltems 加一,表示又添加了一个列表项。

2、列表项插入过程图解

接下来我将通过图解的方法对列表的插入过程进行说明。我们会向一个空的列表中插入三个列表项,这三个列表项的值分别为40,60和50。

 (1)、插入值为40的列表项

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

注意观察插入完成以后列表List 和列表项ListIteml 中各个成员变量之间的变化,比如列表List中的uxNumberOfltems变为了1,表示现在列表中有一个列表项。列表项ListItem1中的pvContainer变成了List,表示此列表项属于列表List。通过图可以看出,列表是一个环形的,即环形列表!

(2)、插入值为60的列表项 

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

上面再讲解函数 vListInsert()的时候说过了列表项是按照升序的方式插入的,所以ListItem2肯定是插入到ListItem1 的后面、xListEnd 的前面。同样的,列表List 的uxNumberOfltems再次加一变为2了,说明此时列表中有两个列表项。

(3)、插入值为60的列表项  

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

按照升序排列的方式,ListItem3应该放到ListIltem1和 ListItem2中间,建议大家最好通过对照这三幅图片来阅读函数vListInsert()的源码,这样就会对函数有一个直观的认识。

(四)、列表项末尾插入 

1、列表项末尾插入函数分析

(1)、与下面的一行代码完成对列表和列表项的完整性检查。
(2)、
从本行开始到(3)之间的代码就是将要插入的列表项插入到列表末尾。使用函数vListInsert()向列表中插入一个列表项的时候这个列表项的位置是通过列表项的值,也就是列表项成员变量xltemValue来确定。vListInsertEnd()是往列表的末尾添加列表项的,我们知道列表中的xListEnd成员变量表示列表末尾的,那么函数vListInsertEnd()插入一个列表项是不是就是插到xListEnd 的前面或后面啊?这个是不一定的,这里所谓的末尾要根据列表的成员变量pxIndex来确定的!前面说了列表中的pxIndex成员变量是用来遍历列表的,pxIndex 所指向的列表项就是要遍历的开始列表项,也就是说pxIndex 所指向的列表项就代表列表头!由于是个环形列表,所以新的列表项就应该插入到pxIndex 所指向的列表项的前面。
(3)、标记新的列表项pxNewListltem属于列表pxList。
(4)、记录列表中的列表项数目的变量加一,更新列表项数目。

2、列表项末尾插入图解 

和函数vListInsert()一样,我们也用图片来看一下函数vListInsertEnd()的插入过程。

(1)、默认列表

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

注意图中列表的pxIndex所指向的列表项,这里为ListIlteml,不再是xListEnd了。

 (2)、插入值为50的列表项

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

(五)、列表项的删除 

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

注意,列表项的删除只是将指定的列表项从列表中删除掉,并不会将这个列表项的内存给释放掉!如果这个列表项是动态分配内存的话。函数uxListRemove()的源码如下:

(1)、要删除一个列表项我们得先知道这个列表项处于哪个列表中,直接读取列表项中的成员变量pvContainer就可以得到此列表项处于哪个列表中。
(2)、与下面一行完成列表项的删除,其实就是将要删除的列表项的前后两个列表项“连接”在一起。
(3)、如果列表的 pxIndex 正好指向要删除的列表项,那么在删除列表项以后要重新给pxIndex找个“对象”啊,这个新的对象就是被删除的列表项的前一个列表项。
(4)、被删除列表项的成员变量pvContainer清零。
(5)、返回新列表的当前列表项数目。

​​​​​​​ (六)、列表的遍历

介绍列表结构体的时候说过列表List_t中的成员变量pxIndex是用来遍历列表的,FreeRTOS提供了一个函数来完成列表的遍历,这个函数是listGET_OWNER_OF_NEXT_ENTRY()。每调用一次这个函数列表的 pxIndex变量就会指向下一个列表项,并且返回这个列表项的pxOwner变量值。这个函数本质上是一个宏,这个宏在文件list.h 中如下定义:

(1)、pxTCB用来保存pxIndex所指向的列表项的pvOwner变量值,也就是这个列表项属于谁的。通常是一个任务的任务控制块。pxList表示要遍历的列表。
(2)、列表的pxIndex变量指向下一个列表项。
(3)、如果pxIndex 指向了列表的xListEnd 成员变量,表示到了列表末尾。
(4)、如果到了列表末尾的话就跳过 xListEnd,pxIndex 再一次重新指向处于列表头的列表项,这样就完成了一次对列表的遍历。
(5)、将pxIndex 所指向的新列表项的pvOwner赋值给pxTCB。此函数用于从多个同优先级的就绪任务中查找下一个要运行的任务。

三、结语 

好啦!关于FreeRTOS列表和列表项的知识就分享记录到此了!最后希望我的分享能给你带来不一样的收获!愿我们一起加油!砥砺前行!

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小小_扫地僧

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

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

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

打赏作者

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

抵扣说明:

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

余额充值