【RTOS学习】源码分析(通用队列 队列 队列集)

🐱作者:一只大喵咪1201
🐱专栏:《RTOS学习》
🔥格言:你只管努力,剩下的交给时间!
图

前面本喵讲解了和任务相关的FreeRTOS源码,进行再来介绍一下用于任务间通信的几种数据结构源码。

目录

  • 🍓通用队列
  • 🍓队列
    • 🍅创建
    • 🍅写数据
    • 🍅读数据
    • 🍅被唤醒
  • 🍓队列集
    • 🍅创建
    • 🍅操作
  • 🍓总结

🍓通用队列

队列(Queue)、队列集(Queue Set)、信号量(Semaphore)、互斥量(Mutex)、递归互斥量,这5种机制的核心都是通用队列(xQueueGenericCreate)

tu
上面函数都调用了xQueueGenericCreate,创建一个通用队列。这个函数原型为:

QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
                                   const UBaseType_t uxItemSize,
                                   const uint8_t ucQueueType );

参数含有如下:

参数含义
uxQueueLength队列长度,对于信号量、互斥量,这个参数为1
uxItemSize队列中数据长度,
对于队列:这个参数由用户设置;
对于队列集:这个参数是sizeof(Queue_t *)
对于信号量、互斥量:这个参数是0
ucQueueType队列类型,分别是:
#define queueQUEUE_TYPE_BASE ( uint8_t ) 0U
#define queueQUEUE_TYPE_SET ( uint8_t ) 0U
#define queueQUEUE_TYPE_MUTEX ( uint8_t ) 1U
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE (uint8_t )2U
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ( uint8_t )3U
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ( uint8_t)4U

根据类型创建不同的类型结构。

tu
如上图所示队列结构体xQUEUE定义。这个结构体可以用来实现队列、队列集、信号量、互斥量:

  • pcWriteTo、xQueue里的pcReadFrom用来维护环形缓冲区:队列/队列集用来读写数据。
  • xTasksWaitingToSend:用来管理"想发送数据,但是没有空间,因此阻塞"的任务,信号量、互斥量不会用到它。
  • xTasksWaitingToReceive:用来管理"想读取数据,但是没有数据,因此阻塞"的任务。
  • uxMessagesWaiting:队列/队列集用来记录有多少个有效数据,信号量/互斥量用来记录数值。

图
如上图所示,创建不同结构的函数虽然不同,但是最终调用的都是xQueueGenericCreate函数,只是传入的参数不同。

🍓队列

这里创建的是可以存放数据的队列。

🍅创建

图
如上图代码所示,使用xQueueCreate创建队列,在调用时传入队列长度和每一项大小两个参数,该函数又是xQueueGenericCreate的封装,本质上就是在创建一个通用队列,只是它的类型是queueQUEUE_TYPE_BASE表明这是一个用来存放数据的队列。

tu
如上图创建通用队列xQueueGenericCreate函数,会使用pvPortMalloc在堆区上开辟一段空间,这段空间包括通用队列结构体Queue_tuxQueueLength * uxItemSize个字节用来存放数据的的空间。

然后再让pucQueueStroage跳过sizeof(Queue_t)个字节,指向存放数据的起始位置,再调用prvInitialiseNewQueue来初始新化队列:

tu
如上图所示prvInitialiseNewQueue函数,先让Queue_t中的pcHead环形缓冲区头指针指向存放数据的起始位置,然后再给uxLengthuxItemSize成员赋值,再调用xQueueGenericReset来复位通用队列。

图

如上图所示xQueueGenericReset函数,关闭中断后进入临界区,让环形缓冲区的尾指针pcTail指向存放数据的末尾位置,让pcWriteTo写指针指向存放数据的起始位置,并且给记录队列中有效数据个数的uxMessageWaiting变量赋值为0。

最重要的时,让读环形缓冲区的指针pcReadFrom指向存放数据的最后一个数据所在位置,并不指向头,而是指向上一次读数据的位置。

然后就是调用vListInitialise来初始化队列结构体Queue_t中的两个链表,一个用来管理因写数据而阻塞的任务,另一个用来管理因读数据而阻塞的任务。最后恢复中断出临界区。


图
如上图所示便是队列创建好后的示意图,总的来说,创建过程分为如下几步:

  1. 在堆区上开辟一段空间,用来存放Queue_t队列头和环形缓冲区。
  2. 让头指针pcHead和写指针pcWrite指向存放数据空间的起始位置。
  3. 让联合体QueuePointers_t中的尾指针pcTail指向数据存满后的最后位置,读指针pcReadFrom指向存放最后一个数据的位置。
  4. 初始化xTasksWaitingToSendxTasksWaitingToReceive两个链表。
  5. Queue_t中其他成员赋予合适的值。

🍅写数据

图
如上图,使用xQueueSned函数向队列中写数据,最后会调用通用写数据函数xQueueGenericSend,在调用时会指定写数据的位置queueSEND_TO_BACK

队列有空:

tu

如上图代码所示,当队列中有空位置时,也就是uxMessageWaiting < uxLength,调用prvCopyDataToQueue将数据复制到环形缓冲区中。


tu
如上图prvCopyDataToQueue函数,使用memcpy将要写入队列的数据复制到队列中,然后更新pcWriteTo写指针,如果和尾指针pcTail相同,则让其指向pcHead来维持环状。最后再让有效数据个数uxMessageWiting加一。


然后再使用listLIST_IS_EMPTY来判断一下管理读取数据链表中是否有任务在等待,如果有,则调用xTaskRemoveFromEventList将其移除。然后写数据成功返回。


图
如上图xTaskRemoveFromEventList函数,首先从等待读取数据的链表中选出要唤醒任务的TCB,然后将TCB从该链表(事件链表)中移除。

如果此时调度器是开着的,则将唤醒的任务放入到就绪链表中,如果是调度器是关着的,则将唤醒的任务放入到xPendingReadyList链表中,待调度器打开后从该链表中将TCB放入就绪链表中。

如果唤醒的任务优先级高于现在正在执行的任务,则发起调度。


队列没空:

tu
如上图xQueueSend函数中队列没空位置的处理代码,如果时间xTicksToWait为0,说明不愿意等待,则立刻返回errQUEUE_FULL表示队列满了,无法写入数据。

如果愿意等待,则调用vTaskInternalSetTimeOutState设置一下时间:

图
如上图所示,就是记录一下当前的系统时间,方便后面进行超时唤醒。


图
在记录完时间以后立刻恢复中断,然后再关闭调度器,因为关闭中断的代价太大了,能关闭调度器就不关中断。

检查一下该任务等待是否超时,如果没有超时则再确认一下队列中真的没空。

  • 因为在恢复中断后,虽然调度器关了,但是该函数xQueueSend随时可能被中断打断,如果打断了,中断函数执行一定时间后再次轮到该函数执行,很有可能就超时了,也有可能中断函数会从队列中读取数据,此时队列就不空了。

然后再调用vTaskPlaceOnEventList将这个写数据的任务放入到等待写数据的链表中。


图
如上图vTaskPlaceOnEventList函数所示,在该函数中,除了将放入到等待写入数据的事件链表中外,还要将自己放入到等待超时时间到来的延时状态链表中。


重新开启调度器。


总的来说,向队列中写数据分为如下几步:

  1. 如果队列不满,则将要写的数据复制到队列中,并且从等待读数据的链表xTasksWaitingToReceive唤醒一个任务(如果有任务在等待的话)。

  2. 如果队列满了,则看该任务是否愿意等待:

    • 不愿意等待,直接错误返回,表示队列满了无法写入。
    • 愿意等待,则将其放入等待写数据的链表xTasksWaitingToSend中,并且根据超时时间也将其放入延时链表xDelayList中。

🍅读数据

队列中有数据:

图
如上图xQueueReceive读取数据的函数,先得到该队列中有效数据个数,如果大于0说明队列中有数据,此时调用prvCopyDataFromQueue函数从队列中复制一个数据到目标地址。

然后调用listLIST_IS_EMPTY判断等待写数据的链表xTasksWaitingTosend中是否有任务,如果有,则此时队列中有空位置,调用xTaskRemoveFromEventList唤醒一个任务。

最后读数据成功返回。

队列没有数据:

图
如上图xQueueReceive函数中部分代码所示,当队列中没有数据时,会先判断该任务是否愿意等待数据到来,如果不愿意,则直接错误返回,表示队列为空。

如果愿意等待,则设置一下超时时间,在恢复中断,关闭调度器后,再次确认是否超时和队列是否为空,原因和前面写数据时一样。

然后将自己的TCB放入到等待读取数据的链表xTasksWaitingToReceive中,然后主动发起一次调度。


总的来说,读数据和写数据非常类似,主要分为如下几步:

  1. 如果队列中有数据,则从队列中将数据复制出去,并且从等待写数据的链表中唤醒任务(如果有任务在等待)。
  2. 如果队列中没有数据:
    • 如果不愿意等待,则直接错误返回。
    • 如果愿意等待,则将自己放入到等待读数据的事件链表中,并且根据超时时间将自己也放入到延时链表中。

🍅被唤醒

回答一个问题,为什么前面本喵讲解的这些代码都在一个for循环中呢?
图
如上图所示,无论是xQueueSend还是xQueueReceive,所有对队列的操作和判断都是在这个死循环for中。

图
如上图代码所示,在将任务放入到事件链表中后会发起一次调度,此时任务本身就处于阻塞状态了。

当被唤醒时,有两种可能:

  • 超时唤醒
  • 可以写数据/读数据

无论哪种情况下被唤醒,该任务都是从阻塞处恢复执行。因为是for循环,所以该任务会重新对队列进行一遍前面的判断和操作,在重新判断和操作的过程中:

  • 被其他任务唤醒:可以写/读数据

图
如上图,在重新判断和操作的过程中,在正常读取数据或者写入数据后,执行return pdPASS成功返回

  • 超时唤醒

图

如上图所示,超时唤醒后,执行return errQUEUE_XXX错误返回

🍓队列集

队列集的核心,就是队列。

🍅创建

图
如上图,调用xQueueCreateSet创建队列集的本质就是调用xQueueGenericCreate来创建通用队列,只是队列类型是queueQUEUE_TYPE_SET,表示这是一个队列集。

图
如上图,其他和创建普通队列一样,只是在prvInitialiseNewQueue中初始化新队列时,将属于队列集的pxQueueSetContainer设置为NULL

图
如上图所示,队列集创建好后,和队列结构几乎相同,只是队列集中每个数据存放的都是Queue_t*队列指针,而且多了一个struct QueueDefinition * pxQueueSetContainer成员,且其初始值是NULL

  • 如果使用队列集的话,普通队列中也会多出pxQueueSetContainer成员,且初始值是NULL

🍅操作

将队列添加到队列集中:

图
如上图xQueueAddToSet函数所示,只是让要添加到队列集中的队列里的pxQueueSetContainer成员指向队列集xQueueSet

也就是说,被添加到的队列集中的队列,都可以通过pxQueueSetContainer指针找到队列集xQueueSet

写队列集:

并没有专门的任务来写队列集,写队列集只是写队列时顺带手的事:

tu
上图所示,在xQueueGenericSend中向队列写数据时,如果使用了队列集,则在调用prvCopyDataToQueue将要写的数据复制到队列中后,再调用prvNotifyQueueSetContainer将队列的地址写入到pxQueueSetContainer指向的队列集中。

图

如上图prvNotifyQueueSetContainer函数,当队列集中有空位置时,将本次写队列的队列地址复制到队列集中,然后判断一下队列集中等待读取数据的链表中是否有任务在等待,如果有则唤醒。

  • 队列集中存放的是队列的句柄。
  • 每当有任务向队列中写数据时,顺手会将要写的队列句柄写到队列集中。
  • 所以队列集的大小就是被添加到队列集中所有队列大小的总和。

只有这样才能在向队列集中写数据时有足够的空间,FreeRTOS对于写队列集也没有阻塞的机制。

对队列集:

tu
如上图,调用xQueueSelectFromSetFromISR读取队列集时,会调用xQueueReceive来读取。

传参时也没有特别传什么参数,所以和普通队列的读取是一样的:

  • 如果队列集中有数据,则读取成功并返回。
  • 如果队列集中没有数据:
    • 不愿意等待则直接错误返回
    • 愿意等待则将自己放入队列集的等待读取数据链表xTasksWaitingToReceive中阻塞等待。

读取队列集成功后返回的是队列集中存放的某个队列句柄:

图
如上图,再使用xQueueReceive从返回的队列句柄指向的队列中读取具体的数据。

🍓总结

无论是普通队列,还是队列集,都是对通用队列的进一步封装,所以说,通用队列才是核心。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数组中的某值,添加到数组的对象中成为新的数组

如图所示 我想要这个数组的第二项time在第一项的里面赋值新key 以此类推 window.KaTeX parse error: Expected }, got EOF at end of input: …dTime window.dayjs().add(1, ‘day’).format(‘YYYY-MM-DD 00:00:00’) v.runState 4 // 最后一个时间截止后无法预估后续的状态…

教师多大年龄退休

老师们&#xff0c;你们知道吗&#xff1f;教师这个职业有一个特别的“退休年龄”。 教师是一个特殊的职业。不仅传授知识&#xff0c;还关心每一个学生的成长&#xff0c;用爱和耐心陪伴他们走过人生的每一个阶段。正是因为教师的工作如此重要&#xff0c;他们的工作年限也是有…

数据分析场景下,企业大模型选型的思路与建议

来源/作者&#xff1a;爱分析 随着大模型带来能力突破&#xff0c;让AI与数据分析相互结合&#xff0c;使分析结果更好支撑业务&#xff0c;促进企业内部数据价值释放&#xff0c;成为了当下企业用户尤为关注的话题。本次分享主要围绕数据分析场景下大模型底座的选型思路&#…

【海报】新年海报 制作

准备一张写好文字的图片。 模型&#xff1a; 电商\lofi_v4.safetensors [9462506675] best quality,masterpiece,8k,(soft lighting:1.2),firecrackers,Chinese new year,<lora:全网首发丨新年红包封面_v1.0:1>, 虚假&#xff0c;不真实&#xff0c;绘画&#xff0c;线条…

Rust语言基础语法使用

1.安装开发工具: RustRover JetBrains: Essential tools for software developers and teams 下载: RustRover: Rust IDE by JetBrains 下载成功后安装并启动RustRover 安装中文语言包插件 重启RustRover生效

在GeoScene产品中发布海图服务——以s57数据标准为例

在GeoScene产品中发布海图服务——以s57数据标准为例1、海图服务部署 GeoScene_Maritime_for_Server海图模块安装完之后&#xff0c;需要在server里面注册海图soe和授权海图许可&#xff0c;如下&#xff1a; 步骤&#xff1a;点击“添加扩展”&#xff0c;从GeoScene_Maritime…

开源微信商城新零售网店,多商户小程序

源码介绍 小玄猪商城是一套基于前后端分离的B2B2C商城系统&#xff0c;支持微信小程序、支付宝小程序、H5商城、APP商城。支持多商户入驻、适用于直播商城、社交电商、团购、拼团、秒杀、砍价、活动报名、客户管理、知识付费、积分商城、抽奖活动、会员卡、权益卡、成长值、预…

1U、2U、4U和42U服务器,看完秒懂!

晚上好&#xff0c;我的网工朋友。 服务器是一个很广泛的概念&#xff0c;涵盖了各种类型和规格的计算机&#xff0c;用于提供各种网络和数据服务。 而机架服务器是当前数据中心和专业计算环境中&#xff0c;使用最为广泛的服务器类型之一。 机架式服务器的外形看来不像计算…

redis:二、缓存击穿的定义、解决方案(互斥锁、逻辑过期)的优缺点和适用场景、面试回答模板和缓存雪崩

缓存击穿的定义 缓存击穿是一种现象&#xff0c;具体就是某一个数据过期时&#xff0c;恰好有大量的并发请求过来&#xff0c;这些并发的请求可能会瞬间把DB压垮。典型场景就是双十一等抢购活动中&#xff0c;首页广告页面的数据过期&#xff0c;此时刚好大量用户进行请求&…

【QT Visual Studio环境配置】error MSB8020: 无法找到 v141/v142 的生成工具(完整版)

首先要了解V**平台工具集根据你安装的Visual Studio版本不同而有所区别&#xff0c;知道这个就容易解决问题了&#xff0c;确定你安装的那个版本&#xff0c;需要使用哪个工具集。 v143–>VS2022v142–>VS2019v141–>VS2017v140–>VS2015v120–>VS2013 一、解决…

uniapp:使用fixed定位,iOS平台的安全区域问题解决

manifest.json > 添加节点 "safearea": { //iOS平台的安全区域"background": "#1C1E22","backgroundDark": "#1C1E22", // HX 3.1.19支持"bottom": {"offset": "auto"} },已解决&#xff…

数据库操作习题12.12

考虑如下的人员数据&#xff0c;其中加下划线的是主码&#xff0c;数据库模式由四个关系组成: employee (empname, street, city) works (empname, compname, salary) company(id, compname, city) managers (empname, mgrname) 其中 关系 employee 给出人员的基本信息,包括人员…

issue queue的实现方式

主要从一下几个点进行考虑&#xff1a; 集中式&#xff08;Centrallized&#xff09;或者分布式(Distributed)&#xff1b;压缩式&#xff08;Compressing&#xff09;或者非压缩式(Non-compressing)&#xff1b;数据捕捉的方式&#xff08;Data-capture&#xff09;或者非数据…

Ubuntu系统使用Nginx搭建RTMP服务器

环境&#xff1a; 推流端 rockpi s 主控rk3308 运行ubuntu系统 服务端 ubuntu 播放器 VLC播放器 服务端安装依赖&#xff1a; apt-get install build-essential libpcre3 libpcre3-dev libssl-dev创建nginx编译目录&#xff1a; mkdir my_nginx_rtmp cd my_nginx_rtmp/下载 …

Linux性能调优技术概览

Linux性能调优技术概览 概述 这里的Linux性能调优主要是关于Linux系统上程序的性能跟踪&#xff0c;因为只有收集到足够的准确的性能数据才能找到程序和系统的性能瓶颈。Linux性能调优的原理、框架、工具等内容包括三个方面&#xff1a; 信息源 通常是以“事件”的形式&#…

X86汇编语言:从实模式到保护模式(代码+注释)--c10、11(保护模式:32位x86处理器编程架构+进入保护模式)

保护模式&#xff1a;32位x86处理器编程架构 IA-32架构的基本执行环境 寄存器扩展 通用寄存器&#xff08;32&#xff09;&#xff1a;EAX EBX ECX EDX ESI EDI EBP ESP 指令寄存器&#xff08;32&#xff09;&#xff1a;EIP 标志寄存器&#xff08;32&#xff09;&#xff…

Hive学习新天地一站式掌握Hive技能,让你成为大数据领域的佼佼者!

介绍&#xff1a;Hive是一个构建在Hadoop顶层的数据仓库工具&#xff0c;起源于Facebook为了解决海量数据的统计分析需求。它能够将结构化的数据文件映射为一张数据库表&#xff0c;并提供类似于SQL的查询功能&#xff0c;可以将SQL语句转换为MapReduce任务进行运行。 Hive的出…

【企业转型】以企业架构为中心的SABOE数字化转型五环法

01 传统企业数字化转型面临诸多挑战 即将过去的2023年&#xff0c;chatGPT大模型、数据资产入表等事件的发生&#xff0c;标志着数字经济正在加速发展。数字经济是人类社会继农业经济、工业经济之后的第三种经济形态&#xff0c;将推动生产方式、生活方式和治理方式深刻变革&a…

21.Servlet 技术

JavaWeb应用的概念 在Sun的Java Servlet规范中&#xff0c;对Java Web应用作了这样定义&#xff1a;“Java Web应用由一组Servlet、HTML页、类、以及其它可以被绑定的资源构成。它可以在各种供应商提供的实现Servlet规范的 Servlet容器 中运行。” Java Web应用中可以包含如下…

教师退休享受国家规定的什么待遇

作为一名教师&#xff0c;一直致力于为学生提供最好的教育服务。然而&#xff0c;随着时间的推移&#xff0c;我们的身体和精力可能会逐渐下降&#xff0c;最终不得不退休。 那么&#xff0c;教师退休后可以享受哪些待遇呢&#xff1f;根据我所了解的情况&#xff0c;以下是一些…