表
2025/8/22大约 9 分钟
表
列表
列表和列表项作为数据结构,都是结构体封装,这个类型的变量而已
列表被用来跟踪FreeRTOS中的任务
用户可以在freeRTOSConfig.h 中自定义:configLIST_VOLATILE
正点原子给的是空
// 选项1:使用 volatile(推荐用于多数情况)
#define configLIST_VOLATILE volatile
// 选项2:空定义(如果确认不需要或编译器足够智能)
#define configLIST_VOLATILE
// 选项3:针对特定编译器的扩展
#define configLIST_VOLATILE __volatile__列表结构体
typedef struct xLIST
{
listFIRST_LIST_INTEGRITY_CHECK_VALUE /*< 如果 configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 设为 1,则设置为已知值,用于数据完整性检查。 */
configLIST_VOLATILE UBaseType_t uxNumberOfItems; /*< 列表中当前包含的列表项数量 */
ListItem_t * configLIST_VOLATILE pxIndex; /*< 用于遍历列表的指针。指向最后一次调用 listGET_OWNER_OF_NEXT_ENTRY() 返回的列表项。 */
MiniListItem_t xListEnd; /*< 包含最大可能值的迷你列表项,意味着它始终位于列表末尾,因此用作结束标记。 */
listSECOND_LIST_INTEGRITY_CHECK_VALUE /*< 如果 configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 设为 1,则设置为已知值,用于数据完整性检查。 */
} List_t;- uxNumberOfItems
- 作用:记录列表中当前有效的列表项数量
- 示例:如果有3个任务在就绪列表中,这个值就是3
- pxIndex :列表遍历指针,用于实现轮询调度 类型
- 工作机制:指向下一个将被执行的列表任务,当该任务被执行时,指向再下一个任务,到达列表底部后会
从头开始
// 当调度器需要选择下一个任务时:
ListItem_t *pxNextItem = listGET_OWNER_OF_NEXT_ENTRY(pxList);
// pxIndex 会指向上次选择的项,下次从它的下一项开始- xListEnd 类型
- 作用:列表的结束标记,是一个特殊的"哨兵"节点
- 特点:
- 值设置为最大可能值(portMAX_DELAY)
- 始终位于列表末尾
- 确保列表遍历能够正确终止

列表项
/*
* 定义列表能够包含的唯一对象类型。
*/
struct xLIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /*< 如果 configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 设为 1,则设置为已知值,用于数据完整性检查。 */
configLIST_VOLATILE TickType_t xItemValue; /*< 列表项的值。在大多数情况下,这个值用于按降序对列表进行排序。 */
struct xLIST_ITEM * configLIST_VOLATILE pxNext; /*< 指向列表中下一个 ListItem_t 的指针。 */
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /*< 指向列表中前一个 ListItem_t 的指针。 */
void * pvOwner; /*< 指向包含此列表项的对象(通常是 TCB)的指针。因此,在包含列表项的对象和列表项本身之间存在双向链接。 */
void * configLIST_VOLATILE pvContainer; /*< 指向此列表项所在列表的指针(如果有的话)。 */
listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE /*< 如果 configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 设为 1,则设置为已知值,用于数据完整性检查。 */
};
typedef struct xLIST_ITEM ListItem_t; /* 由于某些原因,lint 希望这是两个独立的定义。 */
迷你列表项
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;
列表初始化
/*
* 初始化一个列表结构
* pxList: 要初始化的列表指针
*/
void vListInitialise( List_t * const pxList )
{
/* 列表结构包含一个用于标记列表结束的列表项(xListEnd)。
初始化列表时,将列表结束项作为唯一的列表条目插入。 */
/* 将 pxIndex 指向列表结束项 */
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd ); /*lint !e826 !e740 使用迷你列表结构作为列表结束以节省RAM。这是经过检查且有效的。 */
/* 列表结束项的值为列表中可能的最高值,确保它始终位于列表的末尾。
在 FreeRTOS 中,portMAX_DELAY 通常是最大的 TickType_t 值 */
pxList->xListEnd.xItemValue = portMAX_DELAY;
/* 列表结束项的前向和后向指针都指向自身,这样我们就知道列表是空的。
这是一个经典的空双向链表设计: */
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd ); /*lint !e826 !e740 使用迷你列表结构作为列表结束以节省RAM。这是经过检查且有效的。 */
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );/*lint !e826 !e740 使用迷你列表结构作为列表结束以节省RAM。这是经过检查且有效的。 */
/* 初始化列表项数量为 0 */
pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
/* 如果 configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 设为 1,
则向列表写入已知值用于数据完整性检查 */
listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}
列表项初始化
/*-----------------------------------------------------------*/
/*
* 初始化一个列表项
* pxItem: 要初始化的列表项指针
*/
void vListInitialiseItem( ListItem_t * const pxItem )
{
/* 确保列表项没有被记录为在任何列表中(pvContainer 为 NULL) */
pxItem->pvContainer = NULL;
/* 如果 configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 设为 1,
则向列表项写入已知值用于数据完整性检查 */
listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}列表项要根据实际情况来初始化,比如在创建任务函数中
插入列表项
xListEnd是一个虚拟节点,不存储数据,永远在列表末尾
列表: [A] ⇄ [B] ⇄ [xListEnd]
↑
pxIterator 找到位置(在 B 之后)
列表: [A] ⇄ [B] ⇄ [New] ⇄ [xListEnd]
/*
* 向列表中插入一个新的列表项(按 xItemValue 值排序插入)
* pxList: 要插入的目标列表
* pxNewListItem: 要插入的新列表项
*/
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
/* 仅当定义了 configASSERT() 时有效,这些测试可以捕获内存中列表数据结构被覆盖的情况。
它们不会捕获由于 FreeRTOS 错误配置或使用导致的数据错误。 */
listTEST_LIST_INTEGRITY( pxList );
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
/* 将新列表项按 xItemValue 值顺序插入到列表中。
如果列表中已存在具有相同项值的列表项,则新列表项应放在它之后。
这确保了存储在就绪列表中的 TCB(都具有相同的 xItemValue 值)能够公平分享 CPU。
但是,如果 xItemValue 与尾部标记值相同,下面的迭代循环将不会结束。
因此首先检查该值,并在必要时稍微修改算法。 */
if( xValueOfInsertion == portMAX_DELAY )
{
/* 如果插入值等于最大值,直接插入到列表末尾(xListEnd 之前) */
pxIterator = pxList->xListEnd.pxPrevious;
}
else
{
/* 遍历列表,找到正确的插入位置 */
/* 从列表结束项开始,向后遍历直到找到第一个 xItemValue 大于插入值的位置 */
for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd );
pxIterator->pxNext->xItemValue <= xValueOfInsertion;
pxIterator = pxIterator->pxNext )
{
/* 这里不需要做任何操作,只是迭代到想要的插入位置 */
}
}
/* 执行双向链表的标准插入操作 */
/* 1. 新项的 next 指向迭代器后面的项 */
pxNewListItem->pxNext = pxIterator->pxNext;
/* 2. 新项后面的项的前向指针指向新项 */
pxNewListItem->pxNext->pxPrevious = pxNewListItem;
/* 3. 新项的前向指针指向迭代器 */
pxNewListItem->pxPrevious = pxIterator;
/* 4. 迭代器的后向指针指向新项 */
pxIterator->pxNext = pxNewListItem;
/* 记录该项所在的列表。这允许以后快速移除该项。 */
pxNewListItem->pvContainer = ( void * ) pxList;
/* 增加列表中的项数计数 */
( pxList->uxNumberOfItems )++;
}先看序号值xItemValue是不是最大的,不是的话就从尾节点开始序号值,找到的位置就返回这个位置的
最后将新节点插入这个节点前面

列表是环形的,只有一个列表项的时候,这个列表项的前指针和后指针都指向虚拟尾节点,虚拟尾节点也是这样
列表末尾插 [B] ⇄ [C] ⇄ [xListEnd]
↑
pxIndex (当前指向 C)
列表: [A] ⇄ [B] ⇄ [New] ⇄ [C] ⇄ [xListEnd]
↑
pxIndex (仍然指向 C)
/*
* 将新列表项插入到列表的"逻辑末尾"
* 注意:这不是按值排序插入,而是基于当前 pxIndex 位置的插入
* 这种插入方式使得新项成为通过 listGET_OWNER_OF_NEXT_ENTRY() 调用时最后被移除的项
*
* pxList: 要插入的目标列表
* pxNewListItem: 要插入的新列表项
*/
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
/* 获取列表当前的遍历指针位置 */
ListItem_t * const pxIndex = pxList->pxIndex;
/* 仅当定义了 configASSERT() 时有效,这些测试可以捕获内存中列表数据结构被覆盖的情况。
它们不会捕获由于 FreeRTOS 错误配置或使用导致的数据错误。 */
listTEST_LIST_INTEGRITY( pxList );
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
/* 将新列表项插入到 pxList 中,但不是按值排序列表,
而是使新列表项成为调用 listGET_OWNER_OF_NEXT_ENTRY() 时最后被移除的项。
这种插入方式实现了"向后插入",新项插入在 pxIndex 的前面位置。 */
/* 新项的后向指针指向当前遍历位置 */
pxNewListItem->pxNext = pxIndex;
/* 新项的前向指针指向当前遍历位置的前一个项 */
pxNewListItem->pxPrevious = pxIndex->pxPrevious;
/* 仅用于决策覆盖测试(某些测试工具使用) */
mtCOVERAGE_TEST_DELAY();
/* 更新前后节点的指针,完成插入操作 */
pxIndex->pxPrevious->pxNext = pxNewListItem;
pxIndex->pxPrevious = pxNewListItem;
/* 记录该项所在的列表,便于后续快速移除 */
pxNewListItem->pvContainer = ( void * ) pxList;
/* 增加列表中的项数计数 */
( pxList->uxNumberOfItems )++;
}这里末尾并不是虚拟尾节点,pxIndex是遍历指针,遍历从pxIndex开始,也就是说pxIndex就是列表头,列表是环形的,所以尾部是在pxIndex前面的位置
这样新插入的项在轮询调度中会比较晚被选中

注意pxIndex和前面一个图的区并自己定好位置,相当于这个将来要来管理它的
typedef struct tskTaskControlBlock {
ListItem_t xStateListItem; /* "状态分身" - 负责任务的生命周期管理 */
ListItem_t xEventListItem; /* "事件分身" - 负责任务的通信交互管理 */
// 关键:两个分身都指向同一个本体
// ListItem_t结构中有: void *pvOwner; 指向所属的TCB
} tskTCB;// "状态分身"可以同时出现在:
- 就绪列表(准备运行)
- 延时列表(等待时间到期)
- 挂起列表(被主动暂停)
// "事件分身"可以同时出现在:
- 队列等待列表(等待数据)
- 信号量等待列表(等待信号)
- 事件组等待列表(等待事件位)
// 但注意:同一个分身在某一时刻只能在一个列表中!// 场景:任务同时等待延时和队列数据
void vExampleTask( void *pvParameters )
{
for( ;; )
{
// "状态分身"插入延时列表
vTaskDelay( 1000 ); // → xStateListItem 进入 xDelayedTaskList
// "事件分身"插入队列等待列表
xQueueReceive( xQueue, &data, portMAX_DELAY ); // → xEventListItem 进入 xQueue.xTasksWaitingToReceive
}
}
/* 此时任务的状况:
本体:TCB
分身1(xStateListItem):在延时列表中,等待1000个tick
分身2(xEventListItem):在队列等待列表中,等待数据到达
*/// 当延时到期时:
- 系统扫描延时列表找到 xStateListItem
- 通过 pvOwner 找到所属TCB
- 将 xStateListItem 从延时列表移到就绪列表
// 当队列有数据时:
- 系统扫描队列等待列表找到 xEventListItem
- 通过 pvOwner 找到所属TCB
- 如果该任务还在等待(状态分身可能在延时列表),将其唤醒

