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() 获取列表项所属列表 若节点尚未加入列表,容器指针为空
相关推荐
qq_401700411 天前
RTOS址与值 void*任务传参的坑 走void*万能转的天坑
freertos
天吾cc1 天前
FreeRTOS任务创建和删除
freertos
天吾cc2 天前
FreeRTOS中断管理
freertos
天吾cc2 天前
FreeRTOS任务挂起和恢复
freertos
中文英文-我选中文6 天前
8.2 学习探索
freertos
qq_4017004110 天前
FreeRTOS 堆内存管理源码剖析:释放一块内存,空闲块数量反而变少了?
单片机·freertos
List<String> error_P13 天前
使用FreeRTOS搭建工程项目(一)
freertos
@嵌入式扫地僧20 天前
RTOS 面试实战:任务状态切换原理
freertos·rtos·时间片轮转·任务状态切换·抢占式调度
WWJA王文举1 个月前
Bootloader、App 和升级分区之间是什么关系?
stm32·freertos·嵌入式开发·在线升级