环形缓冲区优点及实现

本文介绍了环形缓冲区的概念,强调其在多任务系统中避免数据读写冲突的优点,并通过示例和代码展示了如何使用环形缓冲区提高线程安全性和性能。
摘要由CSDN通过智能技术生成

环形缓冲区优点及实现

一、环形缓冲区概念

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

  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->pR].x;   //获取新x位置
        y1 = positionRingBuffer.positionBuffer[positionRingBuffer->pR].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;
    }
}

  • 10
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
环形缓冲区的C语言实现可以参考以下步骤: 首先,需要创建一个包含所需函数和变量声明的头文件。可以使用引用中提供的RingBuffer.h头文件作为起点。该头文件中定义了环形缓冲区所需的类型和函数声明。 接下来,在源代码文件中引用该头文件,并实现其中声明的函数。我们可以使用引用中提供的main.c代码作为参考。在代码中,可以看到头文件RingBuffer.h被引用,并且在代码中实现了InitRingBuffer、writeRingbuffer、readRingbuffer、releaseRingbuffer等函数。 在InitRingBuffer函数中,首先对环形缓冲区进行初始化。这个函数接受一个指向缓冲区的指针和缓冲区的大小作为参数,并将头指针和尾指针初始化为0。 在writeRingbuffer函数中,首先检查缓冲区是否已满。如果缓冲区已满,则不进行写操作,否则将数据写入缓冲区,并将头指针向前移动一个位置。 在readRingbuffer函数中,首先检查缓冲区是否为空。如果缓冲区为空,则不进行读操作,否则将数据从缓冲区读取出来,并将尾指针向前移动一个位置。 getRingbufferValidLen函数用于获取当前缓冲区中有效数据的长度,即头指针与尾指针之间的距离。 最后,在使用完环形缓冲区后,需要调用releaseRingbuffer函数来释放资源。 综上所述,环形缓冲区的C语言实现包括引用中的RingBuffer.h头文件和引用中的main.c源代码文件,其中实现了InitRingBuffer、writeRingbuffer、readRingbuffer、getRingbufferValidLen和releaseRingbuffer等函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

studyingdda

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值