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

news2024/10/6 1:12:53

🐱作者:一只大喵咪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从返回的队列句柄指向的队列中读取具体的数据。

🍓总结

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

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

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

相关文章

14.单调队列(滑动窗口最大值)、单调队列优化DP【灵神基础精讲】

单调队列&#xff08;滑动窗口最大值&#xff09; 从「维护单调性」的角度上来说&#xff0c;单调队列和单调栈是一样的&#xff0c;一个弹出队尾元素&#xff0c;另一个弹出栈顶元素。在单调栈的基础上&#xff0c;单调队列多了一个「移除队首」的操作&#xff0c;这类似滑动窗…

牛目标检测数据集VOC+YOLO格式4000张

牛是一种古老的哺乳动物&#xff0c;被人类驯化了数千年&#xff0c;成为了人类重要的家畜之一。它们是一种大型草食性动物&#xff0c;主要生活在草原、森林和农村地区。牛的体型较大&#xff0c;体长可达3米&#xff0c;体重可达1500千克。 牛是一种非常有用的动物&#xff0…

【TB作品】STM32 PWM之实现呼吸灯,STM32F103RCT6,晨启

文章目录 完整工程参考资料实验过程 实验任务&#xff1a; 1&#xff1a;实现PWM呼吸灯&#xff0c;定时器产生PWM&#xff0c;控制实验板上的LED灯亮灭&#xff1b; 2&#xff1a;通过任意两个按键切换PWM呼吸灯输出到两个不同的LED灯&#xff0c;实现亮灭效果&#xff1b; 3&…

提升数据中心网络效率:100G QSFP28 LR4光模块的优势分析

数字信息的急剧增长&#xff0c;对高速、高容量网络的需求愈发迫切&#xff0c;数据传输的速度和距离成为了一个关键的挑战。在这个背景下&#xff0c;100G QSFP28 LR4光模块作为一款性能卓越的光模块&#xff0c;为远距离高速传输提供了全新的解决方案。 该产品是专为符合100G…

【网络面试必问(8)】防火墙原理、正向代理、反向代理、缓存服务器、负载均衡和内容分发服务器

接上一篇&#xff1a;【网络面试必问&#xff08;7&#xff09;】聊聊集线器、交换机和路由器 作为一个程序员&#xff0c;其实很少去了解http请求消息在到达服务器之前&#xff0c;所经过的众多组件的&#xff0c;今天借着机会聊一聊标题中提到的正向代理、反向代理、缓存服务…

分段管理及段页管理

一、 分段 程序的逻辑关系被划分为不同的段&#xff0c;每个段有一个段名&#xff0c;并且每个段都从0开始编址。这些段在内存中分配&#xff0c;每个段占据连续的内存空间&#xff0c;但不同段之间可以不相邻。 这种分段管理有一些特点和优势&#xff1a; 逻辑划分&#xff1…

SpringBoot已经禁掉了循环依赖!

还在问循环依赖嘛&#xff1f;SpringBoot已经禁掉了循环依赖&#xff01; 首发2023-12-18 11:26yuan人生 如果现在面试时还有人问你循环依赖&#xff0c;你就这样怼他&#xff1a;循环依赖是一种代码质量低下的表现&#xff0c;springboot2.6之后的版本已经默认禁用了。 Spr…

计算机与自动医疗检查仓:技术革新引领医疗未来

计算机与自动医疗检查仓&#xff1a;技术革新引领医疗未来 一、引言 随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;已经成为现代社会不可或缺的一部分。它们的应用领域日益扩展&#xff0c;从简单的日常任务到复杂…

云原生系列2-GitLab和Jenkins

1、GitLab类似github&#xff0c;是个私有仓库 1、GitLab安装&#xff0c;至少8G内存4核cpu # 查找Gitlab镜像 docker search gitlab/gitlab-ce # gitlab镜像拉取 docker pull gitlab/gitlab-ce # 查看镜像 docker images # 本机先建3个目录&#xff0c;为了gitlab容器通过挂…

【C语言】自定义类型:结构体深入解析(一)

&#x1f308;write in front :&#x1f50d;个人主页 &#xff1a; 啊森要自信的主页 ✏️真正相信奇迹的家伙&#xff0c;本身和奇迹一样了不起啊&#xff01; 欢迎大家关注&#x1f50d;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;>希望看完我的文章对你有小小的帮助&am…

Moonbeam生态项目分析 — — 跨链借贷协议Orbiter One

概览 Orbiter One是一个非托管的借贷协议和DeFi中心&#xff0c;专注于跨链互操作性。通过使用从借贷中赚取的ORB Token铸造的Intergactic Whiskers Brigade NFT&#xff0c;用户可以质押并获得额外奖励&#xff0c;借贷和跨链存款则可以在不离开Moonbeam的情况下无缝参与其他…

听说蚂蚁的职级调整了

上周三听说蚂蚁的职级调整了&#xff0c;让我们来看一下具体的改革方案&#xff1a; 简单地说&#xff0c;就是把原来的 PN 级一拆二&#xff0c;拆成 2N 和 2N1 级。 从本质上来看&#xff0c;就是把原来扁平化的宽职级变多了&#xff0c;相当于 double 了。 那职级变多有什…

初探 Reactor、Proactor 线程模型与 BIO、AIO、NIO

1 前言 工作中或者是技术上经常会遇到 I/O 、线程模型相关的问题&#xff0c;以及同步、异步、阻塞、非阻塞等各种基础问题&#xff0c;之前上学时候的概念认知总是模糊的&#xff0c;一知半解。趁这次了解希望能够更加深入的去了解这方面的知识&#xff0c;于是有了接下来这篇…

AWS 知识一:如何在AWS上启动云AD服务器(详细到极致)

前言&#xff1a; 首先这里指的云AD服务器&#xff0c;只是为了让读友更好理解。云AD服务器在AWS中称为目录。AWS一共提供了4种目录类别&#xff0c;下面我将全程使用AWS托管微软AD这种目录类别进行示例。他完全提供了和Microsoft AD的功能&#xff0c;包括NTLM&#xff0c;Ker…

Android-Binder基本原理

一、进程角度看IPC机制 在Android系统中&#xff0c;每个进程只能运行在自己所拥有的虚拟地址空间。例如&#xff0c;一个4GB的虚拟地址空间&#xff0c;包含3GB的用户空间和1GB的内核空间&#xff0c;内核空间的大小可以通过参数配置进行调整。两个进程之间的用户空间是彼此独…

如何开发一个免费的App

开发一个免费App意味着能够在项目启动初期&#xff0c;以更低成本的方式进行业务的迭代和市场化验证。 互联网发展到2023年&#xff0c;尤其在生成式AI及大模型技术“跃进式”增长的背景下&#xff0c;一个创新式商业模式的起步变得异常艰难。但如果用好工具&#xff0c;那么不…

【网络安全】—计算机网络基础

文章目录 网络必备基础物理层数据链路层与交换机网络模型OSI/TCP对等传输虚拟局域网VLAN静态路由与配置网络地址转换NAT访问控制列表ACLIP协议与IP地址分类子网掩码网关子网划分总结 计算机网络是指将地理位置不同的、功能独立的多台计算机通过通信线路连接起来&#xff0c;以功…

前端基础Vue项目中的插槽使用

概念 简单理解就是组件内部留一个或多个的插槽位置&#xff0c;可供组件传对应的模板代码进去。插槽的出现&#xff0c;让组件变的更加灵活。 1. 匿名插槽 父组件 <son><p>我是父组件通过匿名插槽传输的内容</p></son> 子组件 <template><di…

透过清澈的眼眸:新生儿视力检测的重要性与留意事项

引言&#xff1a; 新生儿的视力发展是其整体感知和认知能力的基础。因此&#xff0c;进行新生儿视力检测是保障他们健康成长的关键一步。本文将深入探讨新生儿视力检测的重要性&#xff0c;并提供父母在这一过程中需要留意的关键事项&#xff0c;以确保宝宝在视觉方面的正常发…

50ms时延工业相机

华睿工业相机A3504CG000 参数配置&#xff1a; 相机端到端理论时延&#xff1a;80ms 厂家同步信息&#xff0c;此款设备帧率上线23fps&#xff0c;单帧时延&#xff1a;43.48ms&#xff0c;按照一图缓存加上传输显示的话&#xff0c;厂家预估时延在&#xff1a;80ms 厂家还有…