目录
列表
列表项
迷你列表项
列表和列表项的关系
列表相关API函数
列表初始化
列表项初始化
列表项插入
列表项末尾插入
列表项删除
列表遍历
在 FreeRTOS 中,列表(List)和列表项(ListItem)是核心数据结构,用于实现任务、队列、信号量等的管理和调度。这些数据结构是 FreeRTOS 实现其调度算法和同步机制的基础。
列表是 FreeRTOS 中的一个数据结构,概念上和链表有点类似,列表被用来跟踪 FreeRTOS中的任务。
列表项就是存放在列表中的项目
列表相当于链表,列表项相当于节点,FreeRTOS 中的列表是一个双向环形链表
列表
列表是由多个列表项组成的集合,用于管理一组实体。列表提供了添加、移除、遍历列表项的操作。 (任务状态随时会发生改变)
typedef struct xLIST
{listFIRST_LIST_INTEGRITY_CHECK_VALUE /* 校验值 */volatile UBaseType_t uxNumberOfItems; /* 列表中的列表项数量 */ListItem_t * configLIST_VOLATILE pxIndex /* 用于遍历列表项的指针 */MiniListItem_t xListEnd /* 末尾列表项 */listSECOND_LIST_INTEGRITY_CHECK_VALUE /* 校验值 */
} List_t;
1、在该结构体中, 包含了两个宏,这两个宏是确定的已知常量, FreeRTOS通过检查这两个常量的值,来判断列表的数据在程序运行过程中,是否遭到破坏 ,该功能一般用于调试, 默认是不开启的
2、成员uxNumberOfItems,用于记录列表中列表项的个数(不包含 xListEnd)
3、成员 pxIndex 用于指向列表中的某个列表项,一般用于遍历列表中的所有列表项
4、成员变量 xListEnd 是一个迷你列表项,排在最末尾(这是一个特殊的列表项,用作列表的末尾标记。它通常不用于存储实际的数据,而是用来优化列表的遍历操作。
xListEnd
列表项通常被插入到列表的末尾,使得在列表末尾插入和删除操作更加高效)
列表项
列表项(地址非连续、人为连接在一起的)是构成列表的基本单元,每个列表项代表了系统中的一个实体,如任务控制块(TCB)、队列、信号量等。
struct xLIST_ITEM
{listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /* 用于检测列表项的数据完整性 */configLIST_VOLATILE TickType_t xItemValue /* 列表项的值 */struct xLIST_ITEM * configLIST_VOLATILE pxNext /* 下一个列表项 */struct xLIST_ITEM * configLIST_VOLATILE pxPrevious /* 上一个列表项 */void * pvOwner /* 列表项的拥有者 */struct xLIST * configLIST_VOLATILE pxContainer; /* 列表项所在列表 */listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE /* 用于检测列表项的数据完整性 */
};
typedef struct xLIST_ITEM ListItem_t;
1、成员变量 xItemValue 为列表项的值,这个值多用于按升序对列表中的列表项进行排序(通常用于表示列表项的优先级或者用于在有序列表中排序。在任务的列表项中,这个值通常是任务的优先级)譬如:A列表项作业时间是10ms B列表项的作业时间是20ms,则排列顺序是A-->B,像短作业优先调度算法一样
2、成员变量 pxNext 和 pxPrevious 分别用于指向列表中列表项的下一个列表项和上一个列表项
3、成员变量 pxOwner 用于指向包含列表项的对象(通常是任务控制块)
4、成员变量 pxContainer 用于指向列表项所在列表。
迷你列表项
迷你列表项也是列表项,但迷你列表项仅用于标记列表的末尾和挂载其他插入列表中的列表项
它通常不用于存储实际的数据,而是用来优化列表的遍历操作。xListEnd
列表项通常被插入到列表的末尾,使得在列表末尾插入和删除操作更加高效
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; /* 下一个列表项 */
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;
1、成员变量 xItemValue 为列表项的值,这个值多用于按升序对列表中的列表项进行排序
2、成员变量 pxNext 和 pxPrevious 分别用于指向列表中列表项的下一个列表项和上一个列表项
3、迷你列表项只用于标记列表的末尾和挂载其他插入列表中的列表项,因此不需要成员变量 pxOwner 和 pxContainer,以节省内存开销
列表和列表项的关系
列表初始状态,以及即将插入的两个列表项如下(根据尾插法插入新的列表项)
列表相关API函数
列表初始化
void vListInitialise( List_t * const pxList )
{ pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd ); (1) pxList->xListEnd.xItemValue = portMAX_DELAY; (2) pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd ); (3) pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd ); (4) pxList->uxNumberOfItems = ( UBaseType_t ) 0U; (5) listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList ); (6) listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList ); (7) }
(1)这行代码设置
pxIndex
成员,这是一个用于快速索引的指针,它指向列表的末尾。xListEnd
是一个MiniListItem_t
类型的成员,它作为一个哨兵节点,表示列表的末尾。这样做的目的是为了优化列表的操作,使得在列表末尾插入和删除操作更加高效。(2)这里将列表结束节点的
xItemValue
设置为portMAX_DELAY
。portMAX_DELAY
是一个定义在 FreeRTOS 配置文件中的常量,代表最大的延迟时间。在列表中,它通常用来表示列表项的最大优先级值,用于有序列表的末端。(3)
pxNext
成员指向下一个列表项。在列表初始化时,列表为空,所以末尾节点的pxNext
指针指向它自己本身,表示没有更多的元素。(4)
pxPrevious
成员指向前一个列表项。同样地,初始化时列表为空,所以末尾节点的pxPrevious
指针也指向它自己本身。(5)
uxNumberOfItems
成员记录列表中当前的列表项数量。在列表初始化时,列表为空,所以这个值被设置为0
。(6)
listSET_LIST_INTEGRITY_CHECK_1_VALUE
宏用于设置列表的第一个完整性检查值。这是 FreeRTOS 的一种机制,用于在运行时检查列表的完整性是否遭到破坏。如果内存被意外修改,这些值将会改变,从而可以检测到错误。(7)
listSET_LIST_INTEGRITY_CHECK_2_VALUE
宏用于设置列表的第二个完整性检查值。与第一个检查值一样,它提供了额外的一层保护,以确保列表数据结构没有被损坏
列表项初始化
void vListInitialiseItem( ListItem_t * const pxItem )
{ pxItem->pvContainer = NULL; (1)listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem ); (2)listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem ); (3)
}
(1)这行代码将列表项的
pvContainer
成员初始化为NULL
。pvContainer
是一个空指针,用于指向包含该列表项的列表。当列表项没有被包含在任何列表中时,它的pvContainer
为NULL
。当列表项被添加到列表中时,它的pvContainer
会被设置为指向该列表。(2)和(3)
这两行代码用于设置列表项的完整性检查值。这是 FreeRTOS 的一种机制,用于在运行时检查列表项的完整性是否遭到破坏。如果内存被意外修改,这些值将会改变,从而可以检测到错误。
- listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE:宏用于设置列表项的第一个完整性检查值。
- listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE:宏用于设置列表项的第二个完整性检查值。
列表项插入
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{ ListItem_t *pxIterator; const TickType_t xValueOfInsertion = pxNewListItem->xItemValue; (1) listTEST_LIST_INTEGRITY( pxList ); (2) listTEST_LIST_ITEM_INTEGRITY( pxNewListItem ); if( xValueOfInsertion == portMAX_DELAY ) (3) { pxIterator = pxList->xListEnd.pxPrevious; (4) } else { for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->\ (5) pxNext->xItemValue <=xValueOfInsertion; pxIterator = pxIterator->pxNext ) { //空循环,什么也不做! } } pxNewListItem->pxNext = pxIterator->pxNext; (6) pxNewListItem->pxNext->pxPrevious = pxNewListItem; pxNewListItem->pxPrevious = pxIterator; pxIterator->pxNext = pxNewListItem; pxNewListItem->pvContainer = ( void * ) pxList; (7) ( pxList->uxNumberOfItems )++; (8)
}
(1)、获取要插入的列表项值,即列表项成员变量xItemValue的值,因为要根据这个值来确 定列表项要插入的位置。
(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)、列表的成员变量uxNumberOfItems加一,表示又添加了一个列表项。
列表项末尾插入
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{ ListItem_t * const pxIndex = pxList->pxIndex; listTEST_LIST_INTEGRITY( pxList ); (1)listTEST_LIST_ITEM_INTEGRITY( pxNewListItem ); pxNewListItem->pxNext = pxIndex; (2) pxNewListItem->pxPrevious = pxIndex->pxPrevious; mtCOVERAGE_TEST_DELAY(); pxIndex->pxPrevious->pxNext = pxNewListItem; pxIndex->pxPrevious = pxNewListItem; pxNewListItem->pvContainer = ( void * ) pxList; (3) ( pxList->uxNumberOfItems )++; (4)}
(1)、与下面的一行代码完成对列表和列表项的完整性检查。
(2)、从本行开始到(3)之间的代码就是将要插入的列表项插入到列表末尾。使用函数 vListInsert()向列表中插入一个列表项的时候这个列表项的位置是通过列表项的值,也就是列表 项成员变量 xItemValue 来确定。vListInsertEnd()是往列表的末尾添加列表项的,我们知道列表 中的xListEnd 成员变量表示列表末尾的,那么函数vListInsertEnd()插入一个列表项是不是就是 插到 xListEnd 的前面或后面啊?这个是不一定的,这里所谓的末尾要根据列表的成员变量 pxIndex 来确定的!前面说了列表中的pxIndex成员变量是用来遍历列表的,pxIndex所指向的 列表项就是要遍历的开始列表项,也就是说pxIndex 所指向的列表项就代表列表头!由于是个 环形列表,所以新的列表项就应该插入到pxIndex所指向的列表项的前面。
(3)、标记新的列表项pxNewListItem属于列表pxList。
(4)、记录列表中的列表项数目的变量加一,更新列表项数目。
列表项删除
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{ List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer; (1) pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious; (2) pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext; mtCOVERAGE_TEST_DELAY(); if( pxList->pxIndex == pxItemToRemove ) { pxList->pxIndex = pxItemToRemove->pxPrevious; (3) } else { mtCOVERAGE_TEST_MARKER(); } pxItemToRemove->pvContainer = NULL; (4) ( pxList->uxNumberOfItems )--; return pxList->uxNumberOfItems; (5)
}
(1)、要删除一个列表项我们得先知道这个列表项处于哪个列表中,直接读取列表项中的成 员变量pvContainer就可以得到此列表项处于哪个列表中。
(2)、与下面一行完成列表项的删除,其实就是将要删除的列表项的前后两个列表项“连接” 在一起。
(3)、如果列表的pxIndex正好指向要删除的列表项,那么在删除列表项以后要重新给 pxIndex找个“对象”啊,这个新的对象就是被删除的列表项的前一个列表项。
(4)、被删除列表项的成员变量pvContainer清零。
(5)、返回新列表的当前列表项数目。
列表遍历
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) (1)
{ \ List_t * const pxConstList = ( pxList ); ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; (2) if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd )(3) { ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; (4) } ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; (5)
}
(1)、pxTCB 用来保存pxIndex所指向的列表项的pvOwner变量值,也就是这个列表项属于 谁的?通常是一个任务的任务控制块。pxList表示要遍历的列表。
(2)、列表的pxIndex 变量指向下一个列表项。
(3)、如果pxIndex 指向了列表的xListEnd成员变量,表示到了列表末尾。
(4)、如果到了列表末尾的话就跳过 xListEnd,pxIndex 再一次重新指向处于列表头的列表 项,这样就完成了一次对列表的遍历。
(5)、将pxIndex 所指向的新列表项的pvOwner赋值给pxTCB。 此函数用于从多个同优先级的就绪任务中查找下一个要运行的任务。