1. 列表和列表项概念
1.1 对C语言中链表的简介
在FreeRTOS中,列表和列表项是非常重要的数据结构,它跟C语言中的链表很相似。在C语言中,链表包括单链表、单循环链表、双向循环链表,如下:
在FreeRTOS中,采用的是双项循环链表,对于双项循环链表,有:
- 链表中包含一个表头(也称头结点)和多个结点,若链表中仅有一个表头,结点数为0,称为空表。
- 每个结点包含两个指针域和储存信息域。其中,两个指针域中一个指向前结点,一个指向后结点。第一个结点和最后一个结点有点特殊,如图3所示,结点1的前结点为表头,后结点为结点2;结点4的前结点为结点3,后结点为表头。储存信息域包含储存的可用数据,一般可在该处设置指针,将其指向储存数据处。
- 表头跟结点不同,它一般只需要包含两个指针域,不需要储存信息域。在实际应用中,在表头中也会加入链表的状态信息,如结点的总数、遍历整个链表的指针等信息。
- 表头位置固定不变,当对链表进行插入或删除等操作,其本质是对链表中结点进行插入或删除。
- 表头和结点的数据成员不同,在实际应用中,一般会定义两个不同的数据类型(结构体),分别用于表头和结点。
- 在循环链表中,有些特殊操作不设置链表表头,而直接设置表尾,如两个链表合并时,仅需一个表的表头与另一个表的表尾相连。双项循环列表是一个圈,对于直接设置表尾这种情况,其理解方式可以看成表头一样。
1.2 FreeRTOS中列表和列表项的理解
理解了C语言中的双向循环链表,就很好理解FreeRTOS中的列表和列表项。
在FreeRTOS中,列表和列表项的数据结构声明和函数定义的源代码都在list.c和list.h中。在list.h中存在三个重要的结构体声明,分别是:
struct xLIST //列表
struct xLIST_ITEM //列表项
struct xMINI_LIST_ITEM //Mini列表项
其中,列表相当于链表,Mini列表项相当于表头,列表项相当于双向链表中的结点。在定义列表时,Mini列表项定义为xListEnd,从字面上看,它表示列表中最后一个列表项。由于链表中是一个圈,首就是尾,尾就是首,可以将其理解为表头,但是定义为xListEnd。另外,在定义一个列表时,其本质是定义一个表头,如果对列表的操作,就是对列表中的列表项操作。
在FreeRTOS中,列表的结构体声明如下:
typedef struct xLIST
{
listFIRST_LIST_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
configLIST_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;
其中:
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;
即列表的结构为:
通过分析列表的结构体声明可知:
- uxNumberOfItems、pxIndex和Mimi列表项组成一个完整列表的“表头”,其中包括列表的状态信息,如uxNumberOfItems表示列表中列表项的数目、pxIndex表示索引,Mimi列表项主要包含指针域,分别指向前一个列表项和后一个列表项。
- MiniListItem_t(Mimi列表项)被包含在List_t(列表)里,它被命名为xListEnd,从字面上可知,它表示列表的最后一个列表项。由于在链表中是首尾相连,首就是尾,尾就是首,可将其理解为表头,但是定义为xListEnd。
- 列表(List_t)中表示列表的表头,如果列表项为0,称为空列表。对某个列表的插入和删除,其本质对列表项的插入和删除。
2. 列表和列表项源代码分析
2.1 列表项
struct xLIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE (1)
configLIST_VOLATILE TickType_t xItemValue; (2)
struct xLIST_ITEM * configLIST_VOLATILE pxNext; (3)
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; (4)
void * pvOwner; (5)
void * configLIST_VOLATILE pvContainer; (6)
listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE (7)
};
如上图,该结构体表示列表项,在源代码list.h中定义,分析如下:
(1)和(5)处:该处检查代码的完整性,分析过程较长,单独列一节,具体分析参考2.4节。
(2)处:表示列表项的数值(后面称项值),为TickType_t 类型,即为uint32_t类型。列表中列表项以项值升序排列,该项值跟任务优先级有关,在任务创建时,一般设为:
(TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority
其中:
configMAX_PRIORITIES 为可使用的最大优先级,在FreeRTOSConfig.h中定义。uxPriority在创建任务时设置的优先级。可以看出,优先级越高,排在越前,且项值越小,但创建任务时设置的优先级uxPriority越高。(3)处:指向下一个列表项的首地址。
(4)处:指向前一个列表项的首地址。
(5)处:Owner译为“拥有者”,即列表项的拥有者,表示该列表项属于哪个数据结构的成员,通常是TCB。
(6)处:Container译为“容器,集装箱”,该指针理解为指向它的“容器”,即列表项所属于的列表,pvContainer将会指向它属于的列表。
2.2 Mini列表项
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;
};
分析如下:
- 在FreeRTOS中称为xMINI_LIST_ITEM,即为xLIST_ITEM的“缩小版”,它跟xLIST_ITEM相比减少了pvOwner指针和pvContainer指针。
- xMINI_LIST_ITEM没有数据保存项,同时没有指向列表的指针,因为在定义时,它就被自动包含在列表中。
- xMINI_LIST_ITEM被包含在列表中,表示最后一个列表项的“表头”,在创建一个列表时,会将xItemValue值设置为portMAX_DELAY。
2.3 列表
typedef struct xLIST
{
listFIRST_LIST_INTEGRITY_CHECK_VALUE (1)
configLIST_VOLATILE UBaseType_t uxNumberOfItems; (2)
ListItem_t * configLIST_VOLATILE pxIndex; (3)
MiniListItem_t xListEnd; (4)
listSECOND_LIST_INTEGRITY_CHECK_VALUE (5)
} List_t;
如上图所示,列表用一个List_t类型的结构体表示,它在list.h中定义,分析如下:
- (1)和(5)处:该处检查代码的完整性,分析过程较长,单独列一节,具体分析参考2.4节。
- (2)处:表示该列表中列表项的数量,不包括自己本身,初始值为0。
- (3)处:指向改列表中列表项的索引,可以遍历列表项。
- (4)处:表示列表中的最后一个列表项,在FreeRTOS中称为MiniListItem_t,与列表项相比它少了pvOwner和pvContainer两个指针,主要用于表头中。
2.4 检查代码完整性
用此功能检测代码的完整性,需要将宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1。在源代码中该宏默认为0,在projdefs.h中定义,如下:
#ifndef configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES
#define configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 0
#endif
在list.h中有如下定义:
#if( configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES == 0 )
/* Define the macros to do nothing. */
#define listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE
#define listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE
#define listFIRST_LIST_INTEGRITY_CHECK_VALUE
#define listSECOND_LIST_INTEGRITY_CHECK_VALUE
#define listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem )
#define listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem )
#define listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList )
#define listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList )
#define listTEST_LIST_ITEM_INTEGRITY( pxItem )
#define listTEST_LIST_INTEGRITY( pxList )
#else
/* Define macros that add new members into the list structures. */
#define listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE TickType_t xListItemIntegrityValue1;
#define listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE TickType_t xListItemIntegrityValue2;
#define listFIRST_LIST_INTEGRITY_CHECK_VALUE TickType_t xListIntegrityValue1;
#define listSECOND_LIST_INTEGRITY_CHECK_VALUE TickType_t xListIntegrityValue2;
/* Define macros that set the new structure members to known values. */
#define listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem ) ( pxItem )->xListItemIntegrityValue1 = pdINTEGRITY_CHECK_VALUE
#define listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem ) ( pxItem )->xListItemIntegrityValue2 = pdINTEGRITY_CHECK_VALUE
#define listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList ) ( pxList )->xListIntegrityValue1 = pdINTEGRITY_CHECK_VALUE
#define listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList ) ( pxList )->xListIntegrityValue2 = pdINTEGRITY_CHECK_VALUE
/* Define macros that will assert if one of the structure members does not
contain its expected value. */
#define listTEST_LIST_ITEM_INTEGRITY( pxItem ) configASSERT( ( ( pxItem )->xListItemIntegrityValue1 == pdINTEGRITY_CHECK_VALUE ) && ( ( pxItem )->xListItemIntegrityValue2 == pdINTEGRITY_CHECK_VALUE ) )
#define listTEST_LIST_INTEGRITY( pxList ) configASSERT( ( ( pxList )->xListIntegrityValue1 == pdINTEGRITY_CHECK_VALUE ) && ( ( pxList )->xListIntegrityValue2 == pdINTEGRITY_CHECK_VALUE ) )
#endif /* configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES */
由上可知,如configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,将有效。如果设置为0,所有的宏定义为空。
对于代码完整性检查的理解,主要分为三步:
(1) 在列表和列表项结构体的开头和结尾分别加上宏定义。
listFIRST_LIST_INTEGRITY_CHECK_VALUE listSECOND_LIST_INTEGRITY_CHECK_VALUE //或 listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE
(2) 在列表和列表项初始化时,给列表或列表项中的宏定义赋初始值。
listSET_LIST_INTEGRITY_CHECK_1_VALUE(pxList ); listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList ); //或 listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem ); listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
(3) 对列表进行操作,如插入列表项时,先对列表和列表项进行检查,执行如下语句。
listTEST_LIST_ITEM_INTEGRITY( pxItem ) //或 listTEST_LIST_INTEGRITY( pxList )
其中:
#define listTEST_LIST_ITEM_INTEGRITY( pxItem ) configASSERT( ( ( pxItem )->xListItemIntegrityValue1 == pdINTEGRITY_CHECK_VALUE ) && ( ( pxItem )->xListItemIntegrityValue2 == pdINTEGRITY_CHECK_VALUE ) )
#define listTEST_LIST_INTEGRITY( pxList ) configASSERT( ( ( pxList )->xListIntegrityValue1 == pdINTEGRITY_CHECK_VALUE ) && ( ( pxList )->xListIntegrityValue2 == pdINTEGRITY_CHECK_VALUE ) )
/**
* ANSI C标准中有几个标准预定义宏(也是常用的):
* __LINE__:在源代码中插入当前源代码行号;
* __FILE__:在源文件中插入当前源文件名;
* __DATE__:在源文件中插入当前的编译日期
* __TIME__:在源文件中插入当前编译时间;
* __STDC__:当要求程序严格遵循ANSI C标准时该标识被赋值为1;
* __cplusplus:当编写C++程序时该标识符被定义。
**/
#define vAssertCalled(char,int) printf("Error:%s,%d\r\n",char,int)
#define configASSERT(x) if((x)==0) vAssertCalled(__FILE__,__LINE__)
如果条件不成立,则最终通过printf()函数打印出错误信息。
3. 列表和列表项API函数
3.1 函数说明
函数 | 描述 |
---|---|
void vListInitialise( List_t * const pxList ) | 列表初始化 |
void vListInitialiseItem( ListItem_t * const pxItem ) | 列表项初始化 |
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem ) | 往列表中(pxList)插入新的列表项(pxNewListItem ) |
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem ) | 插入新的列表项(pxNewListItem )到列表中(pxList)末尾 |
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove ) | 删除列表项 |
3.2 宏定义说明
宏定义 | 描述 |
---|---|
listSET_LIST_ITEM_OWNER( pxListItem, pxOwner ) | 设置列表项的拥有者 ,即TCB |
listGET_LIST_ITEM_OWNER( pxListItem ) | 获取列表项的拥有者,即TCB |
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 ) | 获取pxListItem 列表项的下一个列表项 |
listGET_END_MARKER( pxList ) | 获取列表的最后项标记,即列表中Mini列表的首地址 |
listLIST_IS_EMPTY( pxList ) | 列表中列表项的数量是否为空 |
listCURRENT_LIST_LENGTH( pxList ) | 列表中列表项的数量 |
listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) | 获取下一个结点的Owner,即TCB |
listGET_OWNER_OF_HEAD_ENTRY( pxList ) | 获取表头入口(第一个列表项)的Owner,即TCB |
listIS_CONTAINED_WITHIN( pxList, pxListItem ) | 当前列表项(pxListItem )是否属于列表(pxList) |
listLIST_IS_INITIALISED( pxList ) | 列表是否初始化完成 |
4. 函数源代码分析
4.1 列表初始化
列表初始化通过vListInitialise()函数,函数原型如下:
/********************************************************
参数:pxList :需要初始化的列表
返回:无
*********************************************************/
void vListInitialise( List_t * const pxList )
函数代码如下:
void vListInitialise( List_t * const pxList )
{
/* 将列表的索引指向最后一个列表项,因为此时只有一个列表项xListEnd */
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
/*
* 定义列表项值为最大值portMAX_DELAY,该值portmacro.h中定义
* 每个列表都是按照列表项值升序排列
*/
pxList->xListEnd.xItemValue = portMAX_DELAY;
/* 列表中只包含一个列表项 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 );
}
列表初始化完成后,如下:
4.2 列表项初始化
列表项初始化通过vListInitialiseItem()函数,函数原型如下:
/********************************************************
参数:pxItem:需要初始化的列表项
返回:无
*********************************************************/
void vListInitialiseItem( ListItem_t * const pxItem )
pxItem表示需要初始化的列表项,函数代码如下:
void vListInitialiseItem( ListItem_t * const pxItem )
{
/* 列表项成员pvContainer为NULL,表示该列表项未包含在任何列表中 */
pxItem->pvContainer = NULL;
/* 初始化完整性检查字段的变量 */
listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}
列表初始化完成后,如下:
4.3 列表项插入
列表项插入函数为vListInsert()函数,函数原型如下:
/********************************************************
参数:pxList:列表项要插入的列表
pxNewListItem :要插入的列表项
返回:无
*********************************************************/
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 );
/* 如果插入的列表项项值为 portMAX_DELAY ,则插入到列表中pxList->xListEnd的前一项 */
if( xValueOfInsertion == portMAX_DELAY )
{
pxIterator = pxList->xListEnd.pxPrevious;
}
else
{
/* 通过比较项值,找到列表项插入的地方, pxIterator 指向的列表项中项值大于xValueOfInsertion,则退出 */
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->pvContainer = ( void * ) pxList; ⑤
/* 列表项数量加1 */
( pxList->uxNumberOfItems )++; ⑥
}
如下图,有XItemValue 为1和3的两个列表项,现将xItemValue为2的列表项插入列表中,在程序中通过for()循环找到了插入的位置。此时,pxIterator将会指向XItemValue为1的列表项。
总共需要6步,对应步骤如下:
4.4 列表项末尾插入
列表项末尾插入为vListInsertEnd()函数,函数原型如下:
/********************************************************
参数:pxList:列表项要插入的列表
pxNewListItem :要插入的列表项
返回:无
*********************************************************/
void vListInsertEnd( List_t * const pxList,
ListItem_t * const pxNewListItem )
函数代码如下:
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t * const pxIndex = pxList->pxIndex;
/* 代码完整性检查 */
listTEST_LIST_INTEGRITY( pxList );
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
/*
* 列表项插入到末尾中,其末尾不是xListEnd的末尾,而是把xList看成表头,
* 然后新的列表项作为末尾,即XListEnd的后一项
*/
pxNewListItem->pxNext = pxIndex; ①
pxNewListItem->pxPrevious = pxIndex->pxPrevious; ②
mtCOVERAGE_TEST_DELAY();
pxIndex->pxPrevious->pxNext = pxNewListItem; ③
pxIndex->pxPrevious = pxNewListItem; ④
/* 列表项指向属于它的列表 */
pxNewListItem->pvContainer = ( void * ) pxList; ⑤
/* 列表项数量加1 */
( pxList->uxNumberOfItems )++; ⑥
}
将列表项插入到列表的尾部,即插入到表头的前一项中,插入过程如下:
4.2 列表中列表项的删除
从列表中移除一个列表项使用uxListRemove()函数,其原型如下:
/********************************************************
参数:pxItemToRemove :要移除的列表项
返回:返回列表中列表项的数量
*********************************************************/
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
函数代码如下:
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;
/* 列表项数量减1 */
( pxList->uxNumberOfItems )--;
/* 返回列表的列表项数量 */
return pxList->uxNumberOfItems;
}
从列表中移除列表项步骤如下:
参考资料:
[1]: 正点原子:《STM32F407 FreeRTOS开发手册V1.1.pdf》
[2]: 野火:《FreeRTOS 内核实现与应用开发实战指南.pdf》