列表、列表项的定义以及初始化
列表相当于链表,列表项相当于节点,FreeRTOS中的列表相当于一个双向环形链表。
列表使用指针指向列表项。一个列表(list)下面可能有很多个列表项(list item),每个列表项都有一个指针指向列表。
与列表相关的全部东西都在文件list.c和list.h中。在list.h中定义了一个List_t的结构体。
列表的定义
typedef struct xLIST
{
listFIRST_LIST_INTEGRITY_CHECK_VALUE /*用于检测列表项数据是否完整*/
configLIST_VOLATILE UBaseType_t uxNumberOfItems; /*记录列表中列表项的数量*/
ListItem_t * configLIST_VOLATILE pxIndex; /*用于遍历列表*/
MiniListItem_t xListEnd; /*列表项*/
listSECOND_LIST_INTEGRITY_CHECK_VALUE /*用于检测列表项数据是否完整*/
} List_t;
- 第一句和最后一句用来检查列表的完整性。
需要将宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 设置为1,开启以后会向这两个地方分别添加一个变量xListIntegrityValue1和xListIntegrityValue2,在初始化列表的时候会这两个变量中写入一个特殊的值,默认不开启这个功能。 - uxNumberOfItems 用来记录列表中列表项的数量。
- pxIndex 用来记录当前列表项索引号,用于遍历列表。
- 列表项中最后一个列表项,用来表示列表结束,此变量类型为MiniListItem_t,是一个迷你列表项。
列表项的定义
列表项就是存放在列表中的项目,FreeRTOS提供了两种列表项:列表项和迷你列表项。这两种都在文件list.h中有定义。
xLIST_ITEM列表项
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; /*指向一个任务*/
void * configLIST_VOLATILE pvContainer; /*指向包含该列表项的列表 */
listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE /*用于检测列表项数据是否完整*/
};
typedef struct xLIST_ITEM ListItem_t;
- 第一句和最后一句,用来检查列表项的完整性。
- xItemValue列表项的值。
- pxNext指向下一个列表项。
- pxPrevious指向前一个列表项,和pxNext配合起来实现类似双向链表的功能。
- pvOwner记录此列表项归谁拥有,通常是任务控制块。
- pvContainer用来记录此列表项归哪个列表。
注意与pvOwner的区别,在TCB_t中有两个变量xStateListItem和xEventListItem,这两个变量的类型就是ListItem_t,也就是说这两个成员变量都是列表项,以xStateListItem为例,当创建一个任务以后,xStateListItem的pvOwner就指向这个任务的控制块,表示xStateListItem属于此任务。当任务就绪以后,xStateListItem的变量pvContainer就指向就绪列表,表明此列表项在就绪列表中。
xMINI_LIST_ITEM列表项
末尾列表项就是迷你列表项的一种,这里放末尾列表项的定义图。
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;
- 第一句检测列表项的完整性。
- xItemValue记录列表列表项的值。
- pxNext指向下一个列表项
- pxPrevious指向上一个列表项
迷你列表项比列表项少几个成员变量,迷你列表项有的成员变量,列表项都有。为什么还要使用迷你列表项呢?因为有些情况下,我们不需要这么全的功能,可能只需要其中某几个成员变量,如果此时用列表的话会造成内存浪费。例如上面列表结构体List_t中表示最后一个列表项的成员变量xListEnd 就是MiniListItem_t类型。
列表的初始化
如上图所示,列表初始化,列表的pxindex指向列表末尾项,把末尾的列表项初始化为最大值(0xFFFFFFFF),列表末尾项的前指针和后指针都指向它本身。下面是具体的初始化代码:
void vListInitialise( List_t * const pxList )
{
/*初始化时,列表中只有xListEnd,因此pxIndex指向xListEnd*/
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
/*xListEnd的值初始化为最大值,用于列表项升序排序时,排在最后。*/
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 );
}
列表项的初始化
从xLIST_ITEM列表项可知,链表节点ListItem_t 总共有5个成员,但是初始化的时候只需将pvContainer初始化为空即可。
pvContainer初始化为空表示该节点还没有插入到任何链表。
void vListInitialiseItem( ListItem_t * const pxItem )
{
/* 初始化时,列表项所在列表设为空。 */
pxItem->pvContainer = NULL;
/* 初始化用于检测列表数据完整性的校验值 */
listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}
列表操作
1. 插入列表尾部
- 首先获取列表pxList中指针pxIndex 指向的列表项,如上图,如果是一个新的列表,pxIndex 指向的应该是末尾列表项。
- 将待插入列表项pxNewListItem的pxNext指向末尾列表项, 将待插入列表项pxNewListItem的pxPrevious 指向末尾列表项的pxPrevious。
- 将列表中的末尾列表项的前指向pxPrevious指向插入列表项pxNewListItem。将原来列表项前指向的对象的后指向绑定为pxNewListItem。
- 将添加的新列表项归属到列表中。
- 更新列表中列表项的数量。
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
/* 获取列表pxIndex指向的列表项 */
ListItem_t * const pxIndex = pxList->pxIndex;
/* 仅当同时定义了confgASSERT()时才有效,这些测试可能会捕获正在内存中覆盖的列表数据结构。它们不会捕获由于不正确配置或使用FreeRTOS而导致的数据错误. */
listTEST_LIST_INTEGRITY( pxList );
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
/* 更新待插入列表项的指针成员变量 */
pxNewListItem->pxNext = pxIndex;
pxNewListItem->pxPrevious = pxIndex->pxPrevious;
/* 仅在决策覆盖测试期间使用。*/
mtCOVERAGE_TEST_DELAY();
/* 更新列表中原本列表项的指针成员变量 */
pxIndex->pxPrevious->pxNext = pxNewListItem;
pxIndex->pxPrevious = pxNewListItem;
/* 列表项的归属 */
pxNewListItem->pvContainer = ( void * ) pxList;
/* 更新列表中列表项的数量 */
( pxList->uxNumberOfItems )++;
}
2. 升序插入列表
- 获取要添加列表项的数值,在后面用来比较排列。
- 根据列表项的数值,找到列表项要插入的位置,按照升序排列。如果是列表项数值为最大值,指向末尾列表项。否则,迭代找到列表项要插入的位置。然后根据升序排列,插入列表项,再将列表项加到列表里,列表的数量再加1。
- 如上图,是按照升序排列,将节点插入到链表。假设将一个节点排序辅助值是2的节点插入到有两个节点的链表中,这两个现有的节点的排序辅助值分别是1和3,那么插入过程的示意图具体见图。
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 );
/*寻找节点要插入的位置*/
if( xValueOfInsertion == portMAX_DELAY ) /* 如果待插入列表项的值为最大值 */
{
pxIterator = pxList->xListEnd.pxPrevious;
}
else
{
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 )++;
}
3. 移除一个列表项
传入参数:pxItemToRemove是指向待移除的列表项的指针。
返回参数:移除后列表中列表项的数量。
- 首先通过指向列表项的指针获得列表项中所记录的列表信息,通过pvContainer结构体成员获得。
- 将(需要移除列表项)的(下一个列表项)的(前指针指向对象)指向(需要移除列表项)的(前指针指向对象)。将(需要移除列表项)的(前指针指向对象)的(后指针指向对象)指向(需要移除列表项)的(后指针指向对象)。简单地说,就是把需要移除的列表项在链表中解除指向。
- 如果列表正指向待移除的列表项,将其指向待移除的上一个列表项。(因为这个列表项我们要从列表中移除,所以列表不应该还能指向它。)
- 将待移除的列表项中的列表信息情况。(这样这个列表项就和列表没有任何关系了。)
- 更新列表中列表项的数目。
- 返回移除列表项后,列表中剩余列表项的数目。
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
/* 从列表项的pvContainer中获取列表项所在的列表 */
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;
}