FreeRTOS 列表和列表项
1. 列表和列表项是什么?
FreeRTOS 的列表可以理解成一个双向环形链表:
-
列表
List_t:管理一串列表项,记录列表长度、当前遍历位置和一个结束标记。 -
列表项
ListItem_t:链表中的节点,保存前后节点地址、排序值,以及它属于哪个对象、哪个列表。+-----------------------------------------+ | | v |xListEnd <-> 列表项 A <-> 列表项 B <-> 列表项 C <---------+
^
+---- 环形链表的结束标记;空列表时前后都指向自己
数组中的成员通常连续存放,数量也通常预先确定;列表节点可以分散在不同地址,运行时可插入和移除。任务会不断创建、阻塞、就绪和删除,所以内核用列表来管理任务状态。
2. 两个核心结构
2.1 List_t:列表本身
PPT 中的列表结构主要包含:
| 成员 | 作用 |
|---|---|
uxNumberOfItems |
当前列表项数量,不包含结束标记 xListEnd |
pxIndex |
列表遍历索引,指向遍历过程中使用的位置 |
xListEnd |
迷你列表项,作为环形链表的结束标记;排序值初始化为最大值 |
空列表初始化后,xListEnd 的前后指针都指向它自己,pxIndex 也指向它。这样即使列表为空,遍历和插入逻辑也能通过同一个结束标记处理边界。
2.2 ListItem_t:普通列表项
| 成员 | 作用 |
|---|---|
xItemValue |
列表项的排序值;有序插入时按它确定位置 |
pxNext / pxPrevious |
指向下一个/上一个列表项 |
pxOwner |
该列表项所属的对象;任务列表项中通常指向任务控制块 TCB |
pxContainer |
该列表项当前所在的列表;未加入列表时为空 |
普通列表项通常嵌在其所属对象里。列表只负责把节点串起来,pxOwner 让内核能从节点找到对应的任务或其他对象。
2.3 迷你列表项 MiniListItem_t
迷你列表项比普通列表项字段少,主要用于列表中的 xListEnd 哨兵。它提供列表链接和排序值等必要信息,但不需要像普通节点那样保存一个被管理对象的所有者。
3. 列表项如何表示任务?
任务控制块中通常包含不止一个列表项,例如状态列表项和事件列表项。它们让同一个任务能按不同目的被内核管理:
-
状态列表项:表示任务当前处于就绪、阻塞或挂起等状态管理列表中。
-
事件列表项:任务等待队列、信号量等事件时,用于加入对应的等待列表。
要点:一个具体的列表项一次只能属于一个列表。但一个任务可以通过 TCB 中不同的列表项,同时关联不同用途的列表。例如任务等待事件时,状态列表项和事件列表项可以分别进入相应列表。
4. 列表初始化
vListInitialise():初始化列表
初始化空列表、哨兵和遍历索引:
List_t xList;
vListInitialise(&xList);
初始化后列表长度为 0,pxIndex 指向 xListEnd,哨兵的前后指针也指回自身。
vListInitialiseItem():初始化列表项
ListItem_t xItem;
vListInitialiseItem(&xItem);
初始化时,列表项尚未加入任何列表,其 pxContainer 为空。普通应用通常不直接操作内核任务列表;这些函数主要用于 FreeRTOS 内核或自定义内核对象。
5. 插入列表项
5.1 按排序值插入:vListInsert()
vListInsert(&xList, &xItem);
它按 xItemValue 从小到大查找位置,再将节点链接到双向链表中,同时设置节点的 pxContainer 并增加列表长度。排序值相同时,通常插入到已有相同值节点之后,维持其先后顺序。
列表结束哨兵的值初始化为最大值。若新节点的排序值也是最大值,内核会用专门路径把它放到哨兵之前,不把哨兵当作普通数据节点插入。
5.2 按遍历索引插入:vListInsertEnd()
vListInsertEnd(&xList, &xItem);
它把新节点插在当前 pxIndex 所指节点之前,并更新所属列表和列表长度。这里的"end"是相对于当前遍历索引的位置,用于让节点加入列表时兼顾后续轮转遍历;不应简单理解成永远插在哨兵前的物理尾部。
6. 移除列表项
uxListRemove():移除并返回剩余长度
UBaseType_t uxRemainingItems = uxListRemove(&xItem);
函数通过列表项的 pxContainer 找到列表,重新连接它前后节点,然后:
-
如果
pxIndex正指向待移除项,就把索引移到它的前一个节点。 -
清空待移除项的
pxContainer,表示它不再属于该列表。 -
将列表长度减一,并返回移除后的列表长度。
调用前要确保列表项确实在某个列表中。不要把同一个列表项重复插入,也不要在它仍属于一个列表时再把它插到另一个列表。
7. 遍历和常用宏
FreeRTOS 在遍历时通常移动 pxIndex,并通过列表项的 pxOwner 找到所属任务。常见宏包括:
| 宏 | 含义 |
|---|---|
listLIST_IS_EMPTY(pxList) |
判断列表是否为空 |
listCURRENT_LIST_LENGTH(pxList) |
获取列表项数量 |
listGET_OWNER_OF_NEXT_ENTRY(pxOwner, pxList) |
将遍历索引移到下一项,并取得该项的 owner;环形遍历可实现轮转 |
listGET_OWNER_OF_HEAD_ENTRY(pxList) |
获取列表头部节点的 owner |
listGET_LIST_ITEM_OWNER(pxItem) |
获取列表项所属对象 |
listSET_LIST_ITEM_OWNER(pxItem, pxOwner) |
设置列表项所属对象 |
listGET_LIST_ITEM_VALUE(pxItem) |
获取列表项排序值 |
listSET_LIST_ITEM_VALUE(pxItem, xValue) |
设置列表项排序值 |
listLIST_ITEM_CONTAINER(pxItem) |
获取列表项所在列表 |
这些宏属于内核数据结构接口。应用代码通常不应直接修改 FreeRTOS 内部任务列表,以免破坏调度器状态。
8. 列表如何帮助 FreeRTOS 管理任务?
内核可以为不同目的维护不同列表,例如每个优先级的就绪列表、延时/阻塞列表、挂起列表和事件等待列表。任务状态变化时,内核会移除相应列表项,再把它加入新的目标列表。
任务等待延时:从就绪列表移除 → 加入延时列表
延时到期:从延时列表移除 → 加入对应优先级的就绪列表
任务等待队列:状态列表和事件列表分别记录任务等待状态及等待对象
列表使内核可以动态管理数量和状态不断变化的任务。调度器找到最高优先级就绪列表后,再从列表中取得任务控制块,确定运行对象。
9. 本章使用要点
-
FreeRTOS 列表是双向环形链表;
xListEnd是哨兵,不计入uxNumberOfItems。 -
pxOwner用来从列表项找到所属对象;pxContainer表示列表项当前属于哪个列表。 -
一个具体列表项一次只能加入一个列表;要让同一个对象参与不同列表,通常使用对象内不同的列表项成员。
-
vListInsert()按xItemValue有序插入;vListInsertEnd()插入在当前遍历索引之前。 -
移除后节点的
pxContainer被清空,列表长度减少;返回值是移除后的长度。 -
列表通常是内核管理任务的底层结构。一般应用通过任务、队列和同步 API 使用系统,不直接改内核列表。
10. 本章函数与宏汇总
| 函数或宏 | 中文含义 | 主要用法 |
|---|---|---|
vListInitialise() |
初始化列表 | vListInitialise(&list);初始化哨兵、索引和列表长度 |
vListInitialiseItem() |
初始化普通列表项 | vListInitialiseItem(&item);准备未加入列表的节点 |
vListInsert() |
按排序值插入列表项 | vListInsert(&list, &item);依据 item.xItemValue 升序插入 |
vListInsertEnd() |
在当前遍历索引之前插入列表项 | vListInsertEnd(&list, &item);更新节点所属列表和列表长度 |
uxListRemove() |
移除列表项 | uxListRemove(&item);清除节点容器并返回移除后的列表长度 |
listLIST_IS_EMPTY() |
判断列表是否为空 | listLIST_IS_EMPTY(&list);结果为真表示没有普通列表项 |
listCURRENT_LIST_LENGTH() |
获取列表长度 | listCURRENT_LIST_LENGTH(&list);不包含 xListEnd |
listGET_OWNER_OF_NEXT_ENTRY() |
获取遍历中的下一个 owner | 移动 pxIndex 后通过 pxOwner 取得所属对象 |
listGET_OWNER_OF_HEAD_ENTRY() |
获取头节点的 owner | 常用于取得列表头部节点所代表的对象 |
listGET_LIST_ITEM_OWNER() / listSET_LIST_ITEM_OWNER() |
获取/设置列表项所属对象 | 用于在列表项与任务控制块等 owner 之间建立关联 |
listGET_LIST_ITEM_VALUE() / listSET_LIST_ITEM_VALUE() |
获取/设置列表项排序值 | 读写用于 vListInsert() 排序的 xItemValue |
listLIST_ITEM_CONTAINER() |
获取列表项所属列表 | 若节点尚未加入列表,容器指针为空 |