【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从返回的队列句柄指向的队列中读取具体的数据。

🍓总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

开发新功能,在idea中创建新分支

开发新功能&#xff0c;在idea中新建自己的分支&#xff0c;要在dev分支上创建&#xff0c;步骤如下&#xff1a; 1、idea右下角可以看见当前在dev分支上 2、点击dev&#xff0c;接着点击New Branch 输入分支名 创建后&#xff0c;在Local Branches中就有了 此时可以看到已经切…

漏洞复现-某友CRM系统某接口存在任意文件读取(附漏洞检测脚本)

免责声明 文章中涉及的漏洞均已修复&#xff0c;敏感信息均已做打码处理&#xff0c;文章仅做经验分享用途&#xff0c;切勿当真&#xff0c;未授权的攻击属于非法行为&#xff01;文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直接或者间接的…

揭开`this`的神秘面纱:探索 JavaScript 中的上下文密钥(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

后端接口开发-web前台请求接口对后台数据库增删改查-实例

一、后端接口开发的逻辑是&#xff1a; 1.Application项目启动 2.前台接口Url请求后台 3.Controller控制拿到前台请求参数&#xff0c;传递给中间组件Service 4.Service调用Mapper.java 5. mapper.java映射到mapper.xml中的mybatis语句&#xff0c;类似Sql语句操作数据库 6.其…

Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)

目录 一、前言 二、基于注解配置Bean 1.基本介绍 : 2.应用实例 : 3.注意事项 : 三、手动实现Spring 注解配置机制 1.需求 : 2.思路 : 3.实现 : 3.1 自定义注解类 3.2 自定义配置类 3.3 自定义容器类 3.4 在测试类中进行测试 四、自动装配 0.总述 : 1.AutoWired自动装…

Python 爬虫开发完整环境部署,爬虫核心框架安装

Python 爬虫开发完整环境部署 前言&#xff1a; ​ 关于本篇笔记&#xff0c;参考书籍为 《Python 爬虫开发实战3 》 笔记做出来的一方原因是为了自己对 Python 爬虫加深认知&#xff0c;一方面也想为大家解决在爬虫技术区的一些问题&#xff0c;本篇文章所使用的环境为&#x…

音视频:Ubuntu下安装 FFmpeg 5.0.X

1.安装相关依赖 首可选一&#xff1a; sudo apt-get update sudo apt-get install build-essential autoconf automake libtool pkg-config \libavcodec-dev libavformat-dev libavutil-dev \libswscale-dev libresample-dev libavdevice-dev \libopus-dev libvpx-dev libx2…

思码逸企业版 4.0 特性之一:支持 DevOps 全工具链数据分析

《研发效能实践指南》中有一句话“数据驱动不是万能的&#xff0c;没有数据驱动是万万不能的”&#xff0c;数据对于研发效能管理的重要性可见一斑。所以很多团队在做研发效能度量时都在利用各种工具收集、分析着各种数据。 作为一站式研发效能度量平台&#xff0c;思码逸优先…

【Unity动画】综合案例完结-控制角色动作播放+声音配套

这个案例实现的动作并不复杂&#xff0c;主要包含一个 跳跃动作、攻击动作、还有一个包含三个动画状态的动画混合树。然后设置三个参数来控制切换。 状态机结构如下&#xff1a; 完整代码 using System.Collections; using System.Collections.Generic; using UnityEngine;pu…

Leetcode—1523.在区间范围内统计奇数数目【简单】

2023每日刷题&#xff08;六十三&#xff09; Leetcode—1523.在区间范围内统计奇数数目 实现代码 class Solution { public:int countOdds(int low, int high) {int cnt 0;int l low, r high;while(l < r) {if(l % 2) {break;}l;}while(r > low) {if(r % 2) {break…

【算法Hot100系列】盛最多水的容器

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

基于JAVA+SpringBoot+Vue的前后端分离的大学健康档案管理系统

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 随着社会的发展和科技…