环形缓冲区优点及实现
目录
- 环形缓冲区优点及实现
- 一、环形缓冲区概念
- 二、环形缓冲区优点
- 1、一个有缺陷的数据读写示例
- 2、使用环形缓冲区解决数据读写缺陷
- 三、环形缓冲区实现代码
一、环形缓冲区概念
环形缓冲区是一种特殊的缓冲区,其读指针和写指针都指向同一个缓冲区,通过移动指针来实现数据的读取和写入。环形缓冲区的示意图可以如下:
- 初始状态:环形缓冲区的读指针和写指针都指向第一个缓冲区处。
- 添加数据:向环形缓冲区中添加一个数据后,写指针移动到数据块2的位置,而读指针没有移动。
- 读取和添加:环形缓冲区进行了读取和添加后的状态,可以看到环形缓冲区中已经添加了两个数据,已经读取了一个数据。
环形缓冲区所有的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位置
}
}
此示例中存在缺陷:
- TaskReadPosition获取到x位置并存放到x1变量,此时任务切换到TaskWritePosition
- TaskWritePosition中更新x位置和y位置,再切换到TaskReadPosition
- TaskReadPosition继续获取新的y位置并存放到y1变量
- 此时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;
}
}