环形缓冲区优点及实现

环形缓冲区优点及实现

目录

  • 环形缓冲区优点及实现
    • 一、环形缓冲区概念
    • 二、环形缓冲区优点
      • 1、一个有缺陷的数据读写示例
      • 2、使用环形缓冲区解决数据读写缺陷
    • 三、环形缓冲区实现代码

一、环形缓冲区概念

环形缓冲区是一种特殊的缓冲区,其读指针和写指针都指向同一个缓冲区,通过移动指针来实现数据的读取和写入。环形缓冲区的示意图可以如下:

  1. 初始状态:环形缓冲区的读指针和写指针都指向第一个缓冲区处。
  2. 添加数据:向环形缓冲区中添加一个数据后,写指针移动到数据块2的位置,而读指针没有移动。
  3. 读取和添加:环形缓冲区进行了读取和添加后的状态,可以看到环形缓冲区中已经添加了两个数据,已经读取了一个数据。

环形缓冲区所有的push和pop操作都是在一个固定的存储空间内进行,相比队列方式,少掉了对于缓冲区元素所用存储空间的分配、释放。这是环形缓冲区的一个主要优势。

在这里插入图片描述

二、环形缓冲区优点

1、一个有缺陷的数据读写示例

假设在多任务系统中,对数据有1个写任务和1个读任务:

/* 定义一个位置结构体,包含x位置和y位置 */
typedef struct
{volatile unsigned int x;           /* x位置 */volatile unsigned int y;           /* y位置 */
} position;position sensorPosition;   //定义sensor位置结构体/* 定义一个写位置的任务 */
void TaskWritePosition(void *pvParameters)
{unsigned int x0 = 0;   //定义一个x初始位置unsigned int y0 = 0;   //定义一个y初始位置while(1){getSensorPosition(&x0, &y0);   //获取Sensor的x位置和y位置sensorPosition.x =  x0;   //更新sensorPosition结构体中x位置sensorPosition.y =  y0;   //更新sensorPosition结构体中y位置}
}/* 定义一个读位置的任务 */
void TaskReadPosition(void *pvParameters)
{unsigned int x1 = 0;   //定义一个x位置存放变量unsigned int y1 = 0;   //定义一个y位置存放变量while(1){x1 = sensorPosition.x;   //获取sensorPosition结构体中x位置y1 = sensorPosition.y;   //获取sensorPosition结构体中y位置}
}

此示例中存在缺陷:

  1. TaskReadPosition获取到x位置并存放到x1变量,此时任务切换到TaskWritePosition
  2. TaskWritePosition中更新x位置和y位置,再切换到TaskReadPosition
  3. TaskReadPosition继续获取新的y位置并存放到y1变量
  4. 此时x1变量存放的是sensor上次的x位置,y1变量存放的是sensor最新的y位置,导致x位置与y位置匹配

2、使用环形缓冲区解决数据读写缺陷

注:仅限与2个任务之间的数据传输,若大于2个任务还是会存在线程安全问题!

使用环形缓冲区解决此问题:

/* 定义一个位置结构体,包含x位置和y位置 */
typedef struct
{volatile unsigned int x;           /* x位置 */volatile unsigned int y;           /* y位置 */
} position;//定义一个环形缓冲区,用来存放sensor的x与y位置
typedef struct
{volatile unsigned int pW;           /* 写地址 */volatile unsigned int pR;           /* 读地址 */position positionBuffer[100];  /* 缓冲区空间 */
} position_ring_buffer;position_ring_buffer positionRingBuffer;   //定义一个sensor位置的环形缓冲区,用来存放sensor的x与y位置/* 定义一个写位置的任务 */
void TaskWritePosition(void *pvParameters)
{unsigned int x0 = 0;   //定义一个x初始位置unsigned int y0 = 0;   //定义一个y初始位置while(1){getSensorPosition(&x0, &y0);   //获取Sensor的x位置和y位置/* 获取环形缓冲区写指针的下一个位置 */int i = (positionRingBuffer->pW + 1) % BUFFER_SIZE;if(i != positionRingBuffer->pR)    // 环形缓冲区没有写满{positionRingBuffer.positionBuffer[positionRingBuffer->pW].x = x0;   //更新sensorPosition结构体中x位置positionRingBuffer.positionBuffer[positionRingBuffer->pW].y = y0;   //更新sensorPosition结构体中y位置   positionRingBuffer->pW = i;   //将环形缓冲区的写指针更新为下一个写位置}}
}/* 定义一个读位置的任务 */
int TaskReadPosition(void *pvParameters)
{unsigned int x1 = 0;   //定义一个x位置存放变量unsigned int y1 = 0;   //定义一个y位置存放变量while(1){getSensorPosition(&x0, &y0);   //获取Sensor的x位置和y位置/* 如果环形缓冲区当前的读指针与写指针位置相同表示当前环形缓冲区为空 */if(positionRingBuffer->pR == positionRingBuffer->pW){return -1;}else{/* 将当前环形缓冲区读指针位置的数据传给字符c的地址 */x1 = positionRingBuffer.positionBuffer[positionRingBuffer->pW].x;   //获取新x位置y1 = positionRingBuffer.positionBuffer[positionRingBuffer->pW].y;   //获取新y位置/* 将环形缓冲区读指针的位置更新为下一个读位置 */positionRingBuffer->pR = (positionRingBuffer->pR + 1) % BUFFER_SIZE;return 0;}
}

使用环形缓冲区时存放sensor的x和y位置时,读与写互不干扰:读数据根据读指针读取,只有TaskReadPosition任务能够修改读指针位置;写数据使用写指针写数据,只有TaskWritePosition任务能够修改写指针的位置。读sensor和写sensor互相不干扰。

三、环形缓冲区实现代码

参考韦东山老师代码,ring_buffer.c和ring_buffer.h:

ring_buffer.h

/*  Copyright (s) 2019 深圳百问网科技有限公司*  All rights reserved* * 文件名称:ring_buffer.h* 摘要:*  * 修改历史     版本号        Author       修改内容*--------------------------------------------------* 2021.8.21      v01         百问科技      创建文件*--------------------------------------------------
*/
#ifndef __RING_BUFFER_H
#define __RING_BUFFER_H#include "stm32f1xx_hal.h"#define BUFFER_SIZE 1024        /* 环形缓冲区的大小 */
typedef struct
{volatile unsigned int pW;           /* 写地址 */volatile unsigned int pR;           /* 读地址 */unsigned char buffer[BUFFER_SIZE];  /* 缓冲区空间 */
} ring_buffer;/**  函数名:void ring_buffer_init(ring_buffer *dst_buf)*  输入参数:dst_buf --> 指向目标缓冲区*  输出参数:无*  返回值:无*  函数作用:初始化缓冲区
*/
extern void ring_buffer_init(ring_buffer *dst_buf);/**  函数名:void ring_buffer_write(unsigned char c, ring_buffer *dst_buf)*  输入参数:c --> 要写入的数据*            dst_buf --> 指向目标缓冲区*  输出参数:无*  返回值:无*  函数作用:向目标缓冲区写入一个字节的数据,如果缓冲区满了就丢掉此数据
*/
extern void ring_buffer_write(unsigned char c, ring_buffer *dst_buf);/**  函数名:int ring_buffer_read(unsigned char *c, ring_buffer *dst_buf)*  输入参数:c --> 指向将读到的数据保存到内存中的地址*            dst_buf --> 指向目标缓冲区*  输出参数:无*  返回值:读到数据返回0,否则返回-1*  函数作用:从目标缓冲区读取一个字节的数据,如果缓冲区空了返回-1表明读取失败
*/
extern int ring_buffer_read(unsigned char *c, ring_buffer *dst_buf);#endif /* __RING_BUFFER_H */

ring_buffer.c

/*  Copyright (s) 2019 深圳百问网科技有限公司*  All rights reserved* * 文件名称:ring_buffer.c* 摘要:*  * 修改历史     版本号        Author       修改内容*--------------------------------------------------* 2021.8.21      v01         百问科技      创建文件*--------------------------------------------------
*/#include "ring_buffer.h"/**  函数名:void ring_buffer_init(ring_buffer *dst_buf)*  输入参数:dst_buf --> 指向目标缓冲区*  输出参数:无*  返回值:无*  函数作用:初始化缓冲区
*/
void ring_buffer_init(ring_buffer *dst_buf)
{/* 唤醒缓冲区初始化,将读写指针设置为0 */dst_buf->pW = 0;dst_buf->pR = 0;
}/**  函数名:void ring_buffer_write(unsigned char c, ring_buffer *dst_buf)*  输入参数:c --> 要写入的数据*            dst_buf --> 指向目标缓冲区*  输出参数:无*  返回值:无*  函数作用:向目标缓冲区写入一个字节的数据,如果缓冲区满了就丢掉此数据
*/
void ring_buffer_write(unsigned char c, ring_buffer *dst_buf)
{/* 获取环形缓冲区写指针的下一个位置 */int i = (dst_buf->pW + 1) % BUFFER_SIZE;/* 如果环形缓冲区写指针的下一个位置和读指针不相等代表环形缓冲区未写满,若写满则数据直 接丢弃 */if(i != dst_buf->pR)    // 环形缓冲区没有写满{/* 将字符C写到唤醒缓冲区写指针的位置 */dst_buf->buffer[dst_buf->pW] = c;/* 将环形缓冲区的写指针更新为下一个写位置 */dst_buf->pW = i;}
}/**  函数名:int ring_buffer_read(unsigned char *c, ring_buffer *dst_buf)*  输入参数:c --> 指向将读到的数据保存到内存中的地址*            dst_buf --> 指向目标缓冲区*  输出参数:无*  返回值:读到数据返回0,否则返回-1*  函数作用:从目标缓冲区读取一个字节的数据,如果缓冲区空了返回-1表明读取失败
*/
int ring_buffer_read(unsigned char *c, ring_buffer *dst_buf)
{/* 如果环形缓冲区当前的读指针与写指针位置相同表示当前环形缓冲区为空 */if(dst_buf->pR == dst_buf->pW){return -1;}else{/* 将当前环形缓冲区读指针位置的数据传给字符c的地址 */*c = dst_buf->buffer[dst_buf->pR];/* 将环形缓冲区读指针的位置更新为下一个读位置 */dst_buf->pR = (dst_buf->pR + 1) % BUFFER_SIZE;return 0;}
}

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

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

相关文章

【Docker】容器的相关命令

上一篇:创建,查看,进入容器 https://blog.csdn.net/m0_67930426/article/details/135430093?spm1001.2014.3001.5502 目录 1. 关闭容器 2.启动容器 3.删除容器 4.查看容器的信息 查看容器 1. 关闭容器 从图上来看,容器 aa…

Java-网络爬虫(二)

文章目录 前言一、WebMagic二、使用步骤1. 搭建 Maven 项目2. 引入依赖 三、入门案例四、核心对象&组件1. 核心对象SipderRequestSitePageResultItemsHtml(Selectable) 2. 四大组件DownloaderPageProcessorSchedulerPipeline 上篇:Java-网…

YOLOv5改进 | 损失篇 | VarifocalLoss密集检测专用损失函数 (VFLoss,论文一比一复现)

一、本文介绍 本文给大家带来的是损失函数改进VFLoss损失函数,VFL是一种为密集目标检测器训练预测IoU-aware Classification Scores(IACS)的损失函数,我经过官方的版本将其集成在我们的YOLOv8的损失函数使用上,其中有很多使用的小细节(否则按照官方的版本使用根本拟合不了…

计算机Java项目|基于SpringBoot+Vue的图书个性化推荐系统

项目编号:L-BS-GX-10 一,环境介绍 语言环境:Java: jdk1.8 数据库:Mysql: mysql5.7 应用服务器:Tomcat: tomcat8.5.31 开发工具:IDEA或eclipse 二,项目简介 图片管理系统是一个为学生和…

移动通信原理与关键技术学习(第四代蜂窝移动通信系统)

前言:LTE 标准于2008 年底完成了第一个版本3GPP Release 8的制定工作。另一方面,ITU 于2007 年召开了世界无线电会议WRC07,开始了B3G 频谱的分配,并于2008 年完成了IMT-2000(即3G)系统的演进——IMT-Advanc…

第1章 线性回归

一、基本概念 1、线性模型 2、线性模型可以看成:单层的神经网络 输入维度:d 输出维度:1 每个箭头代表权重 一个输入层,一个输出层 单层神经网络:带权重的层为1(将权重和输入层放在一起) 3、…

【STM32】RTC实时时钟

1 unix时间戳 Unix 时间戳(Unix Timestamp)定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数,不考虑闰秒 时间戳存储在一个秒计数器中,秒计数器为32位/64位的整型变量 世界上所有时区的秒计数器相同,不同时区…

RocketMQ 投递消息方式以及消息体结构分析:Message、MessageQueueSelector

🔭 嗨,您好 👋 我是 vnjohn,在互联网企业担任 Java 开发,CSDN 优质创作者 📖 推荐专栏:Spring、MySQL、Nacos、Java,后续其他专栏会持续优化更新迭代 🌲文章所在专栏&…

Mac 升级ruby 升级brew update

Mac 自身版本是2.x 查看ruby版本号 打开终端 ruby -v 1.brew update 如果报错 这时候brew更新出问题了 fatal: the remote end hung up unexpectedly fatal: early EOF fatal: index-pack failed error: RPC failed; curl 18 HTTP/2 stream 3 was reset fatal: th…

SpringMVC-@RequestMapping注解

0. 多个方法对应同一个请求 RequestMapping("/")public String toIndex(){return "index";}RequestMapping("/")public String toIndex2(){return "index";}这种情况是不允许的,会报错。 1. 注解的功能 RequestMapping注…

SolidUI Gitee GVP

感谢Gitee,我是一个典型“吃软不吃硬”的人。奖励可以促使我进步,而批评往往不会得到我的重视。 我对开源有自己独特的视角,我只参与那些在我看来高于自身认知水平的项目。 这么多年来,我就像走台阶一样,一步一步参与…

Android AAudio

文章目录 基本概念启用流程基本流程HAL层对接数据流计时模型调试 基本概念 AAudio 是 Android 8.0 版本中引入的一种音频 API。 AAudio 提供了一个低延迟数据路径。在 EXCLUSIVE 模式下,使用该功能可将客户端应用代码直接写入与 ALSA 驱动程序共享的内存映射缓冲区…