1.该文件究竟在做什么?
FreeRTOS 里几乎所有“任务排队”都依赖这套链表机制。
它实现的是一种双向循环链表,并且内置尾哨兵节点(统一边界处理)。
2.必须抓住的三个结构角色是什么?
任务节点ListItem_t,带排序键 xItemValue、前后指针、owner、container
struct xLIST_ITEM
{
configLIST_VOLATILE TickType_t xItemValue;
struct xLIST_ITEM * configLIST_VOLATILE pxNext;
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
void * pvOwner;
struct xLIST * configLIST_VOLATILE pxContainer;
};
typedef struct xLIST_ITEM ListItem_t;
尾哨兵轻量节点MiniListItem_t
{
configLIST_VOLATILE TickType_t xItemValue;
struct xLIST_ITEM * configLIST_VOLATILE pxNext;
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;
链表控制块List_t,持有节点数量、遍历游标 pxIndex 和尾哨兵 xListEnd。
typedef struct xLIST
{
listFIRST_LIST_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
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;
3.list.c的核心函数
void vListInitialise( List_t * const pxList )
初始化链表,把尾哨兵的前后都指向自己,并把其值设为最大值。
意义:空链表立刻可用,且遍历终止条件统一。
void vListInitialiseItem( ListItem_t * const pxItem )
把节点标记为“不在任何链表里”(pxContainer = NULL)。
意义:避免重复挂链和脏状态。
void vListInsertEnd( List_t * const pxList,
ListItem_t * const pxNewListItem )
按当前游标位置尾插,不排序。
意义:为同优先级任务轮转提供公平性基础。
void vListInsert( List_t * const pxList,
ListItem_t * const pxNewListItem )
按 xItemValue 升序插入。
意义:延时队列等“按时间排序”的场景直接使用。
特别点:对最大值做了特殊分支,避免和哨兵比较时出现死循环风险。
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
从所属链表中摘除节点,修正游标,数量减一。
意义:任务状态迁移(就绪/阻塞/唤醒)的基础动作。
4. 这个实现里最漂亮的设计点
尾哨兵模式:不需要判断头尾空指针,链表边界统一成“遇到哨兵即结束”。
侵入式节点:节点嵌在任务控制块中,不额外分配链表节点内存,减少碎片和分配开销。
双向绑定:节点有 owner,任务对象里有节点。调度器能在“任务对象”和“链表节点”之间 O(1) 来回切换。
5. 它如何服务调度器
就绪队列:通常用 vListInsertEnd + 轮转游标,保证同优先级时间片公平。
延时队列:通常用 vListInsert,按唤醒 tick 自动有序。
事件等待:任务可通过另一条节点链挂到事件列表,事件到达后再转回就绪。