FreeRTOS 列表和列表项

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 找到列表,重新连接它前后节点,然后:

  1. 如果 pxIndex 正指向待移除项,就把索引移到它的前一个节点。

  2. 清空待移除项的 pxContainer,表示它不再属于该列表。

  3. 将列表长度减一,并返回移除后的列表长度。

调用前要确保列表项确实在某个列表中。不要把同一个列表项重复插入,也不要在它仍属于一个列表时再把它插到另一个列表。

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. 本章使用要点

  1. FreeRTOS 列表是双向环形链表;xListEnd 是哨兵,不计入 uxNumberOfItems。

  2. pxOwner 用来从列表项找到所属对象;pxContainer 表示列表项当前属于哪个列表。

  3. 一个具体列表项一次只能加入一个列表;要让同一个对象参与不同列表,通常使用对象内不同的列表项成员。

  4. vListInsert() 按 xItemValue 有序插入;vListInsertEnd() 插入在当前遍历索引之前。

  5. 移除后节点的 pxContainer 被清空,列表长度减少;返回值是移除后的长度。

  6. 列表通常是内核管理任务的底层结构。一般应用通过任务、队列和同步 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() 获取列表项所属列表 若节点尚未加入列表,容器指针为空
相关推荐
La Pulga3 天前
【FreeRTOS源码学习笔记】空闲任务与阻塞延时
笔记·mcu·学习·嵌入式·freertos·实时操作系统
La Pulga4 天前
【FreeRTOS源码学习笔记】任务切换
单片机·mcu·学习·嵌入式·freertos·实时操作系统
La Pulga6 天前
【FreeRTOS源码学习笔记】链表的实现
数据结构·笔记·stm32·mcu·链表·嵌入式·freertos
La Pulga6 天前
【FreeRTOS源码学习笔记】任务创建
笔记·stm32·mcu·学习·嵌入式·freertos·实时操作系统
天吾cc11 天前
FreeRTOS 事件标志组
freertos
编码追梦人12 天前
基于 ESP32-S3 的便携式多参数环境监护仪设计:从硬件选型到 FreeRTOS 多任务落地
数据库·单片机·嵌入式硬件·mongodb·esp32·freertos·低功耗设计
天吾cc16 天前
FreeRTOS 队列集
freertos
硅基手札16 天前
操作系统设计思想系列 06 · FreeRTOS 深度 — 极简可移植范本
架构·操作系统·freertos·实时操作系统·嵌入式开发·设计思想
qq_4017004121 天前
RTOS址与值 void*任务传参的坑 走void*万能转的天坑
freertos