FreeRTOS实战指南 — 3.2 FreeRTOS中链表的实现

目录

1 FreeRTOS中链表的实现

1.1 实现链表节点

1.2 实现链表根节点

1.3 将节点插入到链表的尾部

1.4 将节点按照升序排列插入到链表

1.5 将节点从链表删除

1.6 节点带参宏小函数

2 链表操作实验


1 FreeRTOS中链表的实现

1.1 实现链表节点

在FreeRTOS操作系统中,任务、事件和定时器等经常需要被添加、移除或重新排序,链表能够高效且灵活的方式来管理动态变化的数据集合。

xLIST_ITEM结构体定义了一个双向链表节点,它包含一个用于排序的辅助值、指向链表中前一个和后一个节点的指针、一个指向拥有该节点的内核对象的指针(通常是任务控制块TCB),以及一个指向该节点所属链表的指针。

struct xLIST_ITEM{TickType_t xItemValue;         /*辅助值,用于帮助节点做顺序排列*/struct xLIST_ITEM * pxNext;      /*指向链表下一个节点*/struct xLIST_ITEM * pxPrevious;  /*指向链表前一个节点*/void * pvOwner;                 /*指向拥有该节点的内核对象,通常是TCB*/void * pvContainer;             /*指向该节点所在的链表*/
};
typedef struct xLIST_ITEM ListItem_t;/*节点数据类型重定义*/

MiniListItem_t是ListItem_t的一个简化版本,通常用于表示链表的起始和结束节点。

struct xMINI_LIST_ITEM
{TickType_t xItemValue;         /*辅助值,用于帮助节点做升序排列*/struct xLIST_ITEM * pxNext;      /*指向链表下一个节点*/struct xLIST_ITEM * pxPrevious;  /*指向链表前一个节点*/
};
typedef struct xMINI_LIST_ITEMMiniListItem_t; /*精简节点数据类型重定义*/

节点示意图如图:

             

xItemValueTickType_t类型的变量,通常用于存储与节点相关的值,比如任务的优先级或者任务的等待时间等,可以帮助在排序链表时确定节点的顺序。TickType_t具体表示16位还是32位,由configUSE_16_BIT_TICKS这个宏决定,当该宏定义为1时,TickType_t为16位,否则为32位。

pxNextxLIST_ITEM类型的指针,它指向链表中的下一个节点。在双向链表中,每个节点都通过pxNext和pxPrevious指针与相邻节点相连。

pxPreviousxLIST_ITEM类型的指针,它指向链表中的前一个节点,使得链表可以双向遍历。

pvOwner指向void类型的指针,它通常用来指向拥有该节点的内核对象。在FreeRTOS中,这通常是任务控制块(TaskControlBlock,TCB),表示这个节点是由哪个任务拥有的。

pvContainer指向void类型的指针,它指向包含该节点的容器,比如任务列表或者队列。

链表节点初始化函数在list.c中实现,初始化的时候只需将pvContainer初始化为空即可,表示该节点还没有插入到任何链表,具体实现见代码清单。

void vListInitialiseItem(ListItem_t*constpxItem)
{/*初始化该节点所在的链表为空,表示节点还没有插入任何链表*/pxItem->pvContainer=NULL;}

1.2 实现链表根节点

链表根节点是链表的头部,它包含了链表的元数据和指向链表中第一个和最后一个节点的指针。

typedef structxLIST
{UBaseType_t uxNumberOfItems; /*链表节点计数器*/ListItem_t * pxIndex;         /*链表节点索引指针*/MiniListItem_t xListEnd;     /*链表最后一个节点*/
}List_t;

链表根结点和完整的数据结构示意图如图:

uxNumberOfItemsUBaseType_t类型的变量,用于存储链表中当前的节点数量。

pxIndex指向ListItem_t类型的指针,它用作链表的索引指针。用于快速访问链表中的节点,特别是在需要遍历链表时。

xListEndMiniListItem_t类型的结构体,它代表链表的最后一个节点。

链表节点初始化函数在list.c中实现,具体实现见代码清单6-10,初始化好的根节点示意图具体见。

void vListInitialise(List_t*constpxList)
{/*将链表索引指针指向最后一个节点*/pxList->pxIndex=(ListItem_t*) &(pxList->xListEnd);/*将链表最后一个节点的辅助排序的值设置为最大,确保该节点就是链表的最后节点*/pxList->xListEnd.xItemValue = portMAX_DELAY;/*将最后一个节点的pxNext和pxPrevious指针均指向节点自身,表示链表为空*/pxList->xListEnd.pxNext=(ListItem_t*) &(pxList->xListEnd);pxList->xListEnd.pxPrevious=(ListItem_t*) &(pxList->xListEnd);/*初始化链表节点计数器的值为0,表示链表为空*/pxList->uxNumberOfItems=(UBaseType_t)0U;
}

链表根结点初始化示意图如图:

pxList->pxIndex=(ListItem_t*) &(pxList->xListEnd)这行代码将链表的索引指针pxIndex设置为指向链表的最后一个节点xListEnd。在初始化时,由于链表为空,索引指针指向最后一个节点,这样在添加第一个节点时,可以很容易地将其插入到链表的末尾。

pxList->xListEnd.xItemValue=portMAX_DELAY这行代码将链表最后一个节点的xItemValue设置为portMAX_DELAY。portMAX_DELAY是FreeRTOS中定义的一个常量,表示最大的延迟时间。由于xItemValue用于排序,将最后一个节点的值设置为最大值可以确保它始终位于链表的末尾。

pxList->xListEnd.pxNext=(ListItem_t*) &(pxList->xListEnd)这行代码将链表最后一个节点的pxNext指针设置为指向自身。在链表为空时,最后一个节点的pxNext和pxPrevious指针都指向自身,形成一个循环,表示链表没有其他节点。

pxList->xListEnd.pxPrevious=(ListItem_t*) &(pxList->xListEnd)这行代码将链表最后一个节点的pxPrevious指针也设置为指向自身。这样,无论是向前遍历还是向后遍历链表,都会遇到这个节点,从而知道已经到达链表的末尾。

pxList->uxNumberOfItems=(UBaseType_t)0U这行代码将链表的节点计数器uxNumberOfItems初始化为0,表示链表在开始时是空的。

1.3 将节点插入到链表的尾部

将节点插入到链表的尾部(可以理解为头部)就是将一个新的节点插入到一个空的链表,具体代码实现见代码。

void vListInsertEnd(List_t* constpxList, ListItem_t* constpxNewListItem)
{ListItem_t* constpxIndex = pxList->pxIndex;pxNewListItem->pxNext = pxIndex;pxNewListItem->pxPrevious = pxIndex->pxPrevious;pxIndex->pxPrevious->pxNext = pxNewListItem;pxIndex->pxPrevious = pxNewListItem;/*记住该节点所在的链表*/pxNewListItem->pvContainer = (void*) pxList;/*链表节点计数器++*/(pxList->uxNumberOfItems)++;
}

1.4 将节点按照升序排列插入到链表

将一个新节点按照其排序值插入到链表中,如果新节点的排序值为portMAX_DELAY,则将其插入到链表末尾;否则,遍历链表找到适当的位置,然后按照升序插入新节点,并更新链表的节点计数器。

void vListInsert(List_t * const pxList, ListItem_t * const pxNewListItem)
{ListItem_t *pxIterator;/*获取节点的排序辅助值*/const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;/* 确定新节点的插入位置 */if (xValueOfInsertion == portMAX_DELAY) {/* 如果排序值是portMAX_DELAY,则插入到链表末尾 */pxIterator = pxList->xListEnd.pxPrevious;} else {/* 否则,遍历链表以找到正确的插入位置 */for (pxIterator = (ListItem_t *)&(pxList->xListEnd);pxIterator->pxNext->xItemValue <= xValueOfInsertion;pxIterator = pxIterator->pxNext) {/* 遍历直到找到第一个xItemValue大于新节点值的节点 */}}/* 将新节点插入链表 */pxNewListItem->pxNext = pxIterator->pxNext;pxNewListItem->pxNext->pxPrevious = pxNewListItem;pxNewListItem->pxPrevious = pxIterator;pxIterator->pxNext = pxNewListItem;/* 记录新节点所在的链表 */pxNewListItem->pvContainer = (void *)pxList;/* 更新链表的节点计数 */(pxList->uxNumberOfItems)++;
}

1.5 将节点从链表删除

uxListRemove函数用于从的链表中移除一个指定的节点。首先获取节点所在的链表,然后更新链表中相邻节点的指针以移除目标节点。如果移除的节点是链表的索引节点,还会更新链表的索引。最后,将节点的容器指针设置为NULL以标记它不再属于任何链表,并减少链表的节点计数,函数返回链表中剩余的节点数量。

UBaseType_t uxListRemove(ListItem_t * const pxItemToRemove)
{/* 获取要删除节点所在的链表 */List_t * const pxList = (List_t *)pxItemToRemove->pvContainer;/* 从链表中移除指定节点 */pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;/* 如果删除的节点是链表索引节点,则更新索引 */if (pxList->pxIndex == pxItemToRemove) {pxList->pxIndex = pxItemToRemove->pxPrevious;}/* 将节点的容器指针设置为空,表示节点已从链表中移除 */pxItemToRemove->pvContainer = NULL;/* 更新链表的节点计数 */(pxList->uxNumberOfItems)--;/* 返回链表中剩余的节点数量 */return pxList->uxNumberOfItems;
}

1.6 节点带参宏小函数

FreeRTOS定义了一系列宏用于操作节点,包括设置和获取节点拥有者、排序值、链表头节点、链表结束标记、判断链表是否为空、获取链表长度以及获取链表第一个节点的拥有者等功能。

/* 设置节点的拥有者(通常是任务控制块TCB) */
#define listSET_LIST_ITEM_OWNER(pxListItem, pxOwner) \((pxListItem)->pvOwner = (void*)(pxOwner))/* 获取节点的拥有者 */
#define listGET_LIST_ITEM_OWNER(pxListItem) \((pxListItem)->pvOwner)/* 设置节点的排序辅助值 */
#define listSET_LIST_ITEM_VALUE(pxListItem, xValue) \((pxListItem)->xItemValue = (xValue))/* 获取节点的排序辅助值 */
#define listGET_LIST_ITEM_VALUE(pxListItem) \((pxListItem)->xItemValue)/* 获取链表头节点的排序辅助值 */
#define listGET_ITEM_VALUE_OF_HEAD_ENTRY(pxList) \(((pxList)->xListEnd).pxNext->xItemValue)/* 获取链表的头节点 */
#define listGET_HEAD_ENTRY(pxList) \(((pxList)->xListEnd).pxNext)/* 获取节点的下一个节点 */
#define listGET_NEXT(pxListItem) \((pxListItem)->pxNext)/* 获取链表的结束标记节点 */
#define listGET_END_MARKER(pxList) \((ListItem_t const*)(&((pxList)->xListEnd)))/* 判断链表是否为空 */
#define listLIST_IS_EMPTY(pxList) \((BaseType_t)(((pxList)->uxNumberOfItems) == (UBaseType_t)0))/* 获取链表的节点数 */
#define listCURRENT_LIST_LENGTH(pxList) \((pxList)->uxNumberOfItems)/* 获取链表第一个节点的拥有者(TCB) */
#define listGET_OWNER_OF_NEXT_ENTRY(pxTCB, pxList) \do { \List_t * const pxConstList = (pxList); \/* 将节点索引指向链表的第一个节点 */ \(pxConstList)->pxIndex = (pxConstList)->pxIndex->pxNext; \/* 确保索引不指向结束标记 */ \if ((void*)(pxConstList)->pxIndex == (void*)&((pxConstList)->xListEnd)) { \(pxConstList)->pxIndex = (pxConstList)->pxIndex->pxNext; \} \/* 获取节点的拥有者,即TCB */ \(pxTCB) = (pxConstList)->pxIndex->pvOwner; \} while (0)

2 链表操作实验

通过下面的代码定义和初始化一个链表及其节点,然后将这些节点按照升序插入到链表中。

  #include "stm32f4xx.h"
#include "list.h"/* 定义链表根节点 */
struct xLIST List_Test;/* 定义节点 */
struct xLIST_ITEM List_Item1;
struct xLIST_ITEM List_Item2;
struct xLIST_ITEM List_Item3;int main(void)
{/* 链表根节点初始化 */vListInitialise(&List_Test); /* 节点 1 初始化 */vListInitialiseItem(&List_Item1); List_Item1.xItemValue = 1;/* 节点 2 初始化 */vListInitialiseItem(&List_Item2);List_Item2.xItemValue = 2;/* 节点 3 初始化 */vListInitialiseItem(&List_Item3);List_Item3.xItemValue = 3;/* 将节点插入链表,按照升序排列 */ vListInsert(&List_Test, &List_Item2);vListInsert(&List_Test, &List_Item1);vListInsert(&List_Test, &List_Item3);for (;;){}
}

程序编译好之后,配置好硬件环境,点击调试按钮,然后全速运行,再然后把 List_Test、 List_Item1 、List_Item2 和 List_Item3 这四个全局变量添加到观察窗口,然后查看这几个数据结构中pxNext 和 pxPrevious 的值和我们理解的数据结构一致。

在运行过程中,可能会报如下的错误,根据报错位置,如果我们调用了list.h头文件,那还必须在调用FreeRTOS.h才行,否则编译时候会直接出现相应的预处理错误。

..\..\FreeRTOS\include\list.h(62): error: #35: #error directive: "FreeRTOS.h must be included before list.h"

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/142105.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

第二界陇剑杯赛-MISC

1 题目名称&#xff1a;hard_web-1 题目内容&#xff1a;1.服务器开放了哪些端口&#xff0c;请按照端口大小顺序提交答案&#xff0c;并以英文逗号隔开(如服务器开放了80 81 82 83端口&#xff0c;则答案为80,81,82,83) 题目分值&#xff1a;100.0 题目难度&#xff1a;容易 …

go语言中的数组指针和指针数组的区别详解

1.介绍 大家知道C语言之所以强大&#xff0c;就是因为c语言支持指针&#xff0c;而且权限特别大&#xff0c;c语言可以对计算机中任何内存的指针进行操作&#xff0c;这样自然而然也会带来一些不安全的因素&#xff0c;所以在golang中&#xff0c;「取消了对指针的一些偏移&…

自动排课管理系统(源代码+论文+开题报告)

一、题目摘要 题目简要说明&#xff1a; 选排课系统功能的设计上&#xff0c;选排课系统可以分为登录、排课和选课3个子系统。登录子系统区分排课者(也即系统的管理者)、教师和学生这三者的不同身份&#xff0c;给出不同的权限&#xff0c;在页面中根据身份判断其相应具有的功…

战斗机检测系统源码分享

战斗机检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Visio…

【K230 实战项目】气象时钟

【CanMV K230 AI视觉】 气象时钟 功能描述&#xff1a;说明HMDI资源3.5寸屏幕 使用方法 为了方便小伙伴们理解&#xff0c;请查看视频 B站连接 功能描述&#xff1a; 天气信息获取&#xff1a;通过连接到互联网&#xff0c;实时获取天气数据&#xff0c;包括温度、湿度、天气状…

您的计算机已被.lcrypt勒索病毒感染?恢复您的数据的方法在这里!

导言 在网络安全领域&#xff0c;勒索病毒已经成为一种威胁极大的恶意软件&#xff0c;其中.lcrypt勒索病毒&#xff08;.lcrypt ransomware&#xff09;是最近出现的一种新的变种。它以加密用户数据并要求赎金为手段&#xff0c;严重影响个人和组织的日常运营。本文91数据恢复…

力扣题解1184

大家好&#xff0c;欢迎来到无限大的频道。 今日继续给大家带来力扣题解。 题目描述&#xff08;简单&#xff09;&#xff1a; 公交站间的距离 环形公交路线上有 n 个站&#xff0c;按次序从 0 到 n - 1 进行编号。我们已知每一对相邻公交站之间的距离&#xff0c;distanc…

【Motion Forecasting】【摘要阅读】BANet: Motion Forecasting with Boundary Aware Network

BANet: Motion Forecasting with Boundary Aware Network 这项工作发布于2022年&#xff0c;作者团队来自于OPPO。这项工作一直被放在arxiv上&#xff0c;并没有被正式发表&#xff0c;所提出的方法BANet在2022年达到了Argoverse 2 test dataset上的SOTA水准。 Method BANet…

计算机三级网络技术总结(一)

RPR环中每一个节点都执行SRP公平算法IEEE 802.11a和g将传输速率提高到54Mbps一个BGP发言人与其他自治系统中的BGP发言人要交换路由信息就要先建立TCP连接在一个区域内的路由器数一般不超过200个进入接口配置模式&#xff1a;Router(config)#interface <接口名> 封装ppp协…

QT 事件 Event 应用

文章目录 一、事件处理过程1. QT 事件应用介绍2. 事件分发过程 二、重写事件案例1. 鼠标事件2. 自定义按钮事件 一、事件处理过程 1. QT 事件应用介绍 众所周知Qt是一个基于C的框架&#xff0c;主要用来开发带窗口的应用程序&#xff08;不带窗口的也行&#xff0c;但不是主流…

数据结构和算法之线性结构

原文出处:数据结构和算法之线性结构 关注码农爱刷题&#xff0c;看更多技术文章&#xff01;&#xff01;&#xff01; 线性结构是一种逻辑结构&#xff0c;是我们编程开发工作应用最广泛的数据结构之一。线性结构是包含n个相同性质数据元素的有限序列。它的基本特征是&…

QT--connect的使用

在qt里面我们可以用connect将信号与槽函数连接器起来&#xff0c;而connect是一个常用的函数&#xff0c;用法也非常简单。 来看一个非常简单的栗子 Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);qpbnew QPushButton(this)…

速通汇编(五)认识段地址与偏移地址,CS、IP寄存器和jmp指令,DS寄存器

一&#xff0c;地址的概念 通常所说的地址指的是某内存单元在整个机器内存中的物理地址&#xff0c;把整个机器内存比作一个酒店&#xff0c;内存单元就是这个酒店的各个房间&#xff0c;给这些房间编的门牌号&#xff0c;类比回来就是内存单元的物理地址 在第一篇介绍debug的…

242. 有效的字母异位词(排序后用Map或者滑动窗口用Map)

文章目录 242. 有效的字母异位词49. 字母异位词分组438. 找到字符串中所有字母异位词 242. 有效的字母异位词 242. 有效的字母异位词 给定两个字符串 s 和t&#xff0c;编写一个函数来判断t是否是s的 字母异位词(字母异位词是通过重新排列不同单词或短语的字母而形成的单词或…

VMware ESXi 7.0U3q macOS Unlocker 集成驱动版更新 OEM BIOS 2.7 支持 Windows Server 2025

VMware ESXi 7.0U3q macOS Unlocker 集成驱动版更新 OEM BIOS 2.7 支持 Windows Server 2025 VMware ESXi 7.0U3q macOS Unlocker & OEM BIOS 2.7 集成网卡驱动和 NVMe 驱动 (集成驱动版) ESXi 7.0U3 标准版集成 Intel 网卡、Realtek USB 网卡 和 NVMe 驱动 请访问原文链…

【Linux进程控制】进程创建|终止

目录 一、进程创建 fork函数 写时拷贝 二、进程终止 想明白&#xff1a;终止是在做什么&#xff1f; 进程退出场景 常见信号码及其含义 进程退出的常见方法 正常终止与异常终止 exit与_exit的区别 一、进程创建 fork函数 在Linux中fork函数是非常重要的函数&#x…

【Python电商项目汇报总结】**采集10万+淘宝商品详情数据注意事项总结汇报**

大家好&#xff0c;今天我想和大家聊聊我们在采集10万淘宝商品详情数据时需要注意的一些关键问题。这不仅仅是一个技术活&#xff0c;更是一场细心与合规的较量。下面&#xff0c;我就用咱们都听得懂的话&#xff0c;一一给大家说道说道。 **一、明确目标&#xff0c;有的放矢…

Autosar BswM配置-手动建立Swc Port实现自定义模式切换

文章目录 前言Mode配置Interface配置Data type mappingBswM配置BswMModeRequestPort配置BswMModeCondition配置BswMLogicalExpression配置BswMDataTypeMappingSetsSWC接口配置RTE接口map代码实现总结前言 客户需求中需要在指定电压范围内允许通信,而目前项目中通信主要由PNC控…

C++从入门到起飞之——继承下篇(万字详解) 全方位剖析!

&#x1f308;个人主页&#xff1a;秋风起&#xff0c;再归来~&#x1f525;系列专栏&#xff1a;C从入门到起飞 &#x1f516;克心守己&#xff0c;律己则安 目录 1、派⽣类的默认成员函数 1.1 四个常⻅默认成员函数 1.2 实现⼀个不能被继承的类 ​编辑 2. 继承与友…

SpringBoot 消息队列RabbitMQ 交换机模式 Fanout广播 Direct定向 Topic话题

介绍 作用是接收生产者发送的消息&#xff0c;并根据某种规则将这些消息路由到一个或多个队列。交换机根据绑定规则和路由键来决定如何将消息分发到队列。简而言之&#xff0c;交换机是消息路由的核心组件&#xff0c;它负责将消息从生产者引导到适当的队列&#xff0c;以便消…