List.h文件
列表项结构体(节点)
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;
迷你列表项结构体(迷你节点)
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; /*< 指向前一个列表项 */
};
列表结构体(类双向链表)
其中 ListItem_t * configLIST_VOLATILE pxIndex ,从字面意思理解,它是列表中一个易变的列表项。
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;
列表获取下一个列表项的所属
先创建一个临时的中间变量(一个名为 pxConstList 的列表),用它接收要遍历的列表;
然后让 pxConstList 列表的节点指向该节点的下一个节点;
如果所指向的节点正好是尾节点,那么就跳过尾节点,再向后指一次,这样就等于指向了列表的头部;
最后把 pxConstList 列表中的节点索引号的归属赋给pxTCB。
#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 ) = ( pxConstList )->pxIndex->pvOwner; \
}
思考
- 为什么要有列表、列表项?
我们知道,一个任务通常有四种状态,运行态,阻塞态,就绪态和挂起态。四个状态也就形成了四个列表,每个列表中存放着不同状态的任务,那么列表项应该就是用来代表这些任务的。不同列表之间的切换就构成了一个任务在不同状态之间的切换,同一个列表内的不同列表项的排列,也就构成了同一种状态中不同优先级的任务之间的排列顺序,即谁先运行,谁后运行。
- 为什么列表中要有 pxIndex 列表项?
前面提到,ListItem_t * configLIST_VOLATILE pxIndex ,从字面意思理解,它指的是列表中一个易变的列表项,这个列表项应该就是用来将某个状态中的某个任务挂载到另一个状态上去,以此满足任务的状态切换。(如由就绪态转换为运行态。)
-
pxTCB的作用是什么?
TCB指的是一个任务控制块,里面存放了一个任务的所有信息,pxTCB可能是存放关于任务控制块的某些信息
List.c文件
列表初始化
void vListInitialise( List_t * const pxList )
{
/*< 将尾列表项作为列表的当前列表项 */
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
/*< 给列表的尾列表项的 xItemValue 赋值为 portMAX_DELAY*/
/*< portMAX_DELAY 在文件 portmacro.h 中被定义为 (uint32_t)0xffffffffUL*/
pxList->xListEnd.xItemValue = portMAX_DELAY;
/*< 让尾列表项指向的前一个列表项和后一个列表项均为尾列表项本身 */
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );
/*< 标记此时列表项个数为0,说明尾列表项不计入列表项总数目 */
pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
/*< 检查列表完整性 */
listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}
列表项初始化
void vListInitialiseItem( ListItem_t * const pxItem )
{
/* 确保列表项没有被记录为在列表中,意味着这个列表项是一个新的列表项,还没有归属 */
pxItem->pvContainer = NULL; // 初始化pvContainer 为 NULL
/*< 检查列表项完整性 */
listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}
从列表尾端插入列表项
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
//获取链表的 pxIndex 结构指针,此指针在链表初始化的时候指向了 xListEnd
ListItem_t * const pxIndex = pxList->pxIndex;
/*< 检查列表和列表项完整性 */
listTEST_LIST_INTEGRITY( pxList );
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
//将要插入的列表项所指向的下一个列表项变为列表的尾列表项
pxNewListItem->pxNext = pxIndex;
//将要插入的列表项所指向的上一个列表项变为列表的尾列表项所指向的上一个列表项
pxNewListItem->pxPrevious = pxIndex->pxPrevious;
/* Only used during decision coverage testing. */
mtCOVERAGE_TEST_DELAY();
//让尾列表项的前一个列表项所指向的下一个列表项为要插入的列表项
pxIndex->pxPrevious->pxNext = pxNewListItem;
//让尾列表项所指向的前一个列表项为要插入的列表项
pxIndex->pxPrevious = pxNewListItem;
/* 标记新的列表项属于列表pxList */
pxNewListItem->pvContainer = ( void * ) pxList;
/* 更新列表项数目 */
( pxList->uxNumberOfItems )++;
}
图解:
- 此时有一个新的列表项NEW;列表项End和列表项Pre(即End的前一个列表项)是链接在一起的;
- 执行 pxNewListItem->pxNext = pxIndex 语句,此时列表项NEW所指向的下一个列表项就成了列表的尾列表项即列表项End;
- 执行 pxNewListItem->pxPrevious = pxIndex->pxPrevious 语句,此时列表项NEW所指向的上一个列表项就成了列表项End的前一个列表项即列表项Pre;
-
执行 pxIndex->pxPrevious->pxNext = pxNewListItem 语句,断开列表项End和Pre的一半联系,并让Pre和NEW建立另一半联系,即断开Pre指向End的指针,链接Pre指向NEW的指针。
-
执行 pxIndex->pxPrevious = pxNewListItem 语句,断开列表项End和Pre的另一半联系,并让End和NEW建立另一半联系,即断开End指向Pre的指针,链接End指向NEW的指针。
这样一来,就将一个新的列表项插入尾列表项的前面了。
从列表任意位置插入列表项
插入的方式是按照升序的方式插入的
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
//创建一个新的列表项,用来作为中间项
ListItem_t *pxIterator;
//获取要插入的列表项的xItemValue值,这个值是用来给列表项排序的
//相当于一个列表中列表项的序号
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
listTEST_LIST_INTEGRITY( pxList );
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
//如果要插入的列表项序号等于portMAX_DELAY,那么要插入的位置就是列表的尾端
if( xValueOfInsertion == portMAX_DELAY )
{
//插入的位置为列表的尾节点前面
pxIterator = pxList->xListEnd.pxPrevious;
}
else
{
//否则获取要插入的位置
//注意:freertos中的列表是一个环形列表,xListEnd也可以类似看作是列表的头列表项。
//代码的意思是从头节点开始遍历,遍历到所插入的节点的标号为止,插入的位置一直往后移
for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd );
pxIterator->pxNext->xItemValue <= xValueOfInsertion;
pxIterator = pxIterator->pxNext )
{
/* There is nothing to do here, just iterating to the wanted insertion position. */
}
}
//下列四步就是将列表项插入到列表中,插入的位置即序号为xItemValue
pxNewListItem->pxNext = pxIterator->pxNext;
pxNewListItem->pxNext->pxPrevious = pxNewListItem;
pxNewListItem->pxPrevious = pxIterator;
pxIterator->pxNext = pxNewListItem;
//列表项的成员变量记录该列表项所属的列表
pxNewListItem->pvContainer = ( void * ) pxList;
//列表的列表项数目加1
( pxList->uxNumberOfItems )++;
}
插入过程图解分为两类:一类是按顺序向后插入,一类是在两个列表项中间插入。(仅图解不同)
第一类:按顺序向后插入
1.
2.
3.
4.
5.
第二类:在两个列表项中间插入
1.
2.
3.
4.
5.
删除列表项
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;
/* Only used during decision coverage testing. */
mtCOVERAGE_TEST_DELAY();
//如果要删除的节点正好是列表的pxTndex指向的节点,那么删除节点后给pxIndex重新分配节点
if( pxList->pxIndex == pxItemToRemove )
{
//分配的节点为被移除节点的前一个节点
pxList->pxIndex = pxItemToRemove->pxPrevious;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//被删除列表项的成员变量pvContainer清零,表示该节点不再属于任何列表
pxItemToRemove->pvContainer = NULL;
//列表项的数目减1
( pxList->uxNumberOfItems )--;
//返回新列表的当前列表项的数目
return pxList->uxNumberOfItems;
}