【嵌入式基础】环形缓冲区ring buffer

目录

1.概述

2. 原理

3. 使用场景

4. 实践

4.1 Github搜索

4.2 编写makefile

4.3 编译运行

5.总结


1.概述

ring buffer称作环形缓冲区,也称作环形队列(circular queue),是一种用于表示一个固定尺寸、头尾相连的缓冲区的数据结构,适合缓存数据流。如下为环形缓冲区(ring buffer) 的概念示意图。

在任务间的通信、串口数据收发、log缓存、网卡处理网络数据包、音频/视频流处理中均有环形缓冲区(ring buffer) 的应用。在RT-Thread的ringbuffer.cringbuffer.h文件中,Linux内核文件kfifo.hkfifo.c中也有环形缓冲区(ring buffer)的代码实现。

图片

环形缓冲区的一些使用特点如下:

  • 当一个数据元素被读取出后,其余数据元素不需要移动其存储位置;

  • 适合于事先明确了缓冲区的最大容量的情形。缓冲区的容量(长度)一般固定,可以用一个静态数组来充当缓冲区,无需重复申请内存;

  • 如果缓冲区的大小需要经常调整,就不适合用环形缓冲区,因为在扩展缓冲区大小时,需要搬移其中的数据,这种场合使用链表更加合适;

  • 因为缓冲区成头尾相连的环形,写操作可能会覆盖未及时读取的数据,有的场景允许这种情况发生,有的场景又严格限制这种情况发生。选择何种策略和具体应用场景相关。

2. 原理

站在巨人的肩膀上,有文章已经介绍的很好了,参考文章:

https://zhuanlan.zhihu.com/p/534098236

3. 使用场景

只要出现数据生产者和的可能速度快于数据消费者场景,就需要使用缓存buffer. 比如在汽车ECU控制器中(如座舱域控制器)MCU和SOC之间的通信(如MCU收到CAN数据,根据情况上传给SOC)肯定需要使用缓存buffer, 这个buffer还需要具有先进行出的特性,那么就需要ring buffer.

4. 实践

如果是学习的话,我们当然可以从0到1自己写一个,这样更加能锻炼我们的代码能力。如果是项目着急使用的话,我们首先考虑站在巨人的肩膀上看有没有开源现成的可以参考。

4.1 Github搜索

github上搜索ringbuffer, 找一个star数相对比较高的项目。

图片

读下项目的README

图片

项目在实际硬件平台上使用过,且有相关的测试代码,但是没有makefile,我们需要编写一个makefile来测试下。

Github链接:https://github.com/netube99/RingBuffer

4.2 编写makefile

makefile参考本公众号的系类文章:

编译链接专题第7篇-变量的高级主题(下)

#makefile
COMPILER := gcc
TARGET := ringbuffer.exe
OBJS := ring_buffer.o ring_buffer_chapter.o main.o
RM := del

$(TARGET) : $(OBJS)
  $(COMPILER) -o $@ $^
    
$(OBJS) : %.o : %.c
  $(COMPILER) -o $@ -c $^
    
.PHONY : rebuild clean all

rebuild : clean all

all : $(TARGET)  
  
clean :
  $(RM) *o $(TARGET)

测试代码:

//引用相关头文件
#include <stdint.h>
#include <stdio.h>
#include "ring_buffer.h"

//创建一个数组作为数据存储空间
#define BUFFER_SIZE 128
static uint8_t buffer[BUFFER_SIZE];

//创建环形缓冲区句柄
static ring_buffer rb;

int main(void)
{
    //初始化环形缓冲区参数
    RB_Init(&rb, buffer, BUFFER_SIZE);

    //写入向环形缓冲区写入数据
    RB_Write_String(&rb, "hello world", 11);
    RB_Write_Byte(&rb, '!');
    RB_Write_Byte(&rb, 0x00);

    //删除环形缓冲区部分数据
    RB_Delete(&rb, 2);

    //获取已储存的数据长度
    uint32_t num = RB_Get_Length(&rb);

    //读出环形缓冲区中的数据并打印
    uint8_t get[16];
    RB_Read_String(&rb, get, num);
    printf("%s", get);
    
    //控制台输出内容
    //llo world!
    return 0;
}

4.3 编译运行

编译这个开源项目,生成可执行文件ringbuffer.exe

图片

执行ringbuffer.exe,结果符合测试代码的预期。

5.总结

本文没有详细赘述ringbuffer的原理(参考文章中有详细介绍),着重介绍了项目中需要使用ringbuffer的场景,同时介绍了实际工程中如何从github中获取开源项目并编译运行起来。

ringbuffer的源码如下:

ring_buffer.h


/**
 * \file ring_buffer.h
 * \brief 简易环形缓冲相关定义与声明
 * \author netube_99\netube@163.com
 * \date 2022.08.20
 * \version v0.4.0
*/
#ifndef _RING_BUFFER_H_
#define _RING_BUFFER_H_

//返回值定义
#define RING_BUFFER_SUCCESS     0x01
#define RING_BUFFER_ERROR       0x00

//环形缓冲区结构体
typedef struct
{
    uint32_t head ;             //操作头指针
    uint32_t tail ;             //操作尾指针
    uint32_t Length ;           //已储存的数据量
    uint8_t *array_addr ;       //缓冲区储存数组基地址
    uint32_t max_Length ;       //缓冲区最大可储存数据量
}ring_buffer;

uint8_t RB_Init(ring_buffer *rb_handle, uint8_t *buffer_addr ,uint32_t buffer_size);               //初始化基础环形缓冲区
uint8_t RB_Delete(ring_buffer *rb_handle, uint32_t Length);                                        //从头指针开始删除指定长度的数据
uint8_t RB_Write_Byte(ring_buffer *rb_handle, uint8_t data);                                       //向缓冲区尾指针写一个字节
uint8_t RB_Write_String(ring_buffer *rb_handle, uint8_t *input_addr, uint32_t write_Length);       //向缓冲区尾指针写指定长度数据
uint8_t RB_Read_Byte(ring_buffer *rb_handle, uint8_t *output_addr);                                //从缓冲区头指针读一个字节
uint8_t RB_Read_String(ring_buffer *rb_handle, uint8_t *output_addr, uint32_t read_Length);        //从缓冲区头指针读指定长度数据
uint32_t RB_Get_Length(ring_buffer *rb_handle);                                                    //获取缓冲区里已储存的数据长度
uint32_t RB_Get_FreeSize(ring_buffer *rb_handle);                                                  //获取缓冲区可用储存空间

#endif//#ifndef _RING_BUFFER_H_

ring_buffer.c


/**
 * \file ring_buffer.c
 * \brief 简易环形缓冲的实现
 * \author netube_99\netube@163.com
 * \date 2022.08.20
 * \version v0.4.0
*/

#include <stdint.h>
#include <string.h>
#include "ring_buffer.h"

/**
 * \brief 初始化新缓冲区
 * \param[out] rb_handle: 待初始化的缓冲区结构体句柄
 * \param[in] buffer_addr: 外部定义的缓冲区数组,类型必须为 uint8_t
 * \param[in] buffer_size: 外部定义的缓冲区数组空间
 * \return 返回缓冲区初始化的结果
 *      \arg RING_BUFFER_SUCCESS: 初始化成功
 *      \arg RING_BUFFER_ERROR: 初始化失败
*/
uint8_t RB_Init(ring_buffer *rb_handle, uint8_t *buffer_addr ,uint32_t buffer_size)
{
    //缓冲区数组空间必须大于2且小于数据类型最大值
    if(buffer_size < 2 || buffer_size == 0xFFFFFFFF)
        return RING_BUFFER_ERROR ; //初始化失败
    rb_handle->head = 0 ; //复位头指针
    rb_handle->tail = 0 ; //复位尾指针
    rb_handle->Length = 0 ; //复位已存储数据长度
    rb_handle->array_addr = buffer_addr ; //缓冲区储存数组基地址
    rb_handle->max_Length = buffer_size ; //缓冲区最大可储存数据量
    return RING_BUFFER_SUCCESS ; //缓冲区初始化成功
}

/**
 * \brief 从头指针开始删除指定长度的数据
 * \param[out] rb_handle: 缓冲区结构体句柄
 * \param[in] Length: 要删除的长度
 * \return 返回删除指定长度数据结果
 *      \arg RING_BUFFER_SUCCESS: 删除成功
 *      \arg RING_BUFFER_ERROR: 删除失败
*/
uint8_t RB_Delete(ring_buffer *rb_handle, uint32_t Length)
{
    if(rb_handle->Length < Length)
        return RING_BUFFER_ERROR ;//已储存的数据量小于需删除的数据量
    else
    {
        if((rb_handle->head + Length) >= rb_handle->max_Length)
            rb_handle->head = Length - (rb_handle->max_Length - rb_handle->head);
        else
            rb_handle->head += Length ;    //头指针向前推进,抛弃数据
        rb_handle->Length -= Length ;      //重新记录有效数据长度
        return RING_BUFFER_SUCCESS ;//已储存的数据量小于需删除的数据量
    }
}

/**
 * \brief 向缓冲区尾部写一个字节
 * \param[out] rb_handle: 缓冲区结构体句柄
 * \param[in] data: 要写入的字节
 * \return 返回缓冲区写字节的结果
 *      \arg RING_BUFFER_SUCCESS: 写入成功
 *      \arg RING_BUFFER_ERROR: 写入失败
*/
uint8_t RB_Write_Byte(ring_buffer *rb_handle, uint8_t data)
{
    //缓冲区数组已满,产生覆盖错误
    if(rb_handle->Length == (rb_handle->max_Length))
        return RING_BUFFER_ERROR ;
    else
    {
        *(rb_handle->array_addr + rb_handle->tail) = data;//基地址+偏移量,存放数据
        rb_handle->Length ++ ;//数据量计数+1
        rb_handle->tail ++ ;//尾指针后移
    }
    //如果尾指针超越了数组末尾,尾指针指向缓冲区数组开头,形成闭环
    if(rb_handle->tail > (rb_handle->max_Length - 1))
        rb_handle->tail = 0 ;
  return RING_BUFFER_SUCCESS ;
}

/**
 * \brief 从缓冲区头指针读取一个字节
 * \param[out] rb_handle: 缓冲区结构体句柄
 * \param[out] output_addr: 读取的字节保存地址
 * \return 返回读取状态
 *      \arg RING_BUFFER_SUCCESS: 读取成功
 *      \arg RING_BUFFER_ERROR: 读取失败
*/
uint8_t RB_Read_Byte(ring_buffer *rb_handle, uint8_t *output_addr)
{
    if (rb_handle->Length != 0)//有数据未读出
    {
        *output_addr = *(rb_handle->array_addr + rb_handle->head);//读取数据
        rb_handle->head ++ ;
        rb_handle->Length -- ;//数据量计数-1
        //如果头指针超越了数组末尾,头指针指向数组开头,形成闭环
        if(rb_handle->head > (rb_handle->max_Length - 1))
            rb_handle->head = 0 ;
        return RING_BUFFER_SUCCESS ;
    }
    return RING_BUFFER_ERROR ;
}

/**
 * \brief 向缓冲区尾部写指定长度的数据
 * \param[out] rb_handle: 缓冲区结构体句柄
 * \param[out] input_addr: 待写入数据的基地址
 * \param[in] write_Length: 要写入的字节数
 * \return 返回缓冲区尾部写指定长度字节的结果
 *      \arg RING_BUFFER_SUCCESS: 写入成功
 *      \arg RING_BUFFER_ERROR: 写入失败
*/
uint8_t RB_Write_String(ring_buffer *rb_handle, uint8_t *input_addr, uint32_t write_Length)
{
    //如果不够存储空间存放新数据,返回错误
    if((rb_handle->Length + write_Length) > (rb_handle->max_Length))
        return RING_BUFFER_ERROR ;
    else
    {
        //设置两次写入长度
        uint32_t write_size_a, write_size_b ;
        //如果顺序可用长度小于需写入的长度,需要将数据拆成两次分别写入
        if((rb_handle->max_Length - rb_handle->tail) < write_Length)
        {
            write_size_a = rb_handle->max_Length - rb_handle->tail ;//从尾指针开始写到储存数组末尾
            write_size_b = write_Length - write_size_a ;//从储存数组开头写数据
            //分别拷贝a、b段数据到储存数组中
            memcpy(rb_handle->array_addr + rb_handle->tail, input_addr, write_size_a);
            memcpy(rb_handle->array_addr, input_addr + write_size_a, write_size_b);
            rb_handle->Length += write_Length ;//记录新存储了多少数据量
            rb_handle->tail = write_size_b ;//重新定位尾指针位置
        }
        else//如果顺序可用长度大于或等于需写入的长度,则只需要写入一次
        {
            write_size_a = write_Length ;//从尾指针开始写到储存数组末尾
            memcpy(rb_handle->array_addr + rb_handle->tail, input_addr, write_size_a);
            rb_handle->Length += write_Length ;//记录新存储了多少数据量
            rb_handle->tail += write_size_a ;//重新定位尾指针位置
            if(rb_handle->tail == rb_handle->max_Length)
                rb_handle->tail = 0 ;//如果写入数据后尾指针刚好写到数组尾部,则回到开头,防止越位
        }
        return RING_BUFFER_SUCCESS ;
    }
}

/**
 * \brief 从缓冲区头部读指定长度的数据,保存到指定的地址
 * \param[out] rb_handle: 缓冲区结构体句柄
 * \param[out] output_addr: 读取的数据保存地址
 * \param[in] read_Length: 要读取的字节数
 * \return 返回缓冲区头部读指定长度字节的结果
 *      \arg RING_BUFFER_SUCCESS: 读取成功
 *      \arg RING_BUFFER_ERROR: 读取失败
*/
uint8_t RB_Read_String(ring_buffer *rb_handle, uint8_t *output_addr, uint32_t read_Length)
{
    if(read_Length > rb_handle->Length)
        return RING_BUFFER_ERROR ;
    else
    {
        uint32_t Read_size_a, Read_size_b ;
        if(read_Length > (rb_handle->max_Length - rb_handle->head))
        {
            Read_size_a = rb_handle->max_Length - rb_handle->head ;
            Read_size_b = read_Length - Read_size_a ;
            memcpy(output_addr, rb_handle->array_addr + rb_handle->head, Read_size_a);
            memcpy(output_addr + Read_size_a, rb_handle->array_addr, Read_size_b);
            rb_handle->Length -= read_Length ;//记录剩余数据量
            rb_handle->head = Read_size_b ;//重新定位头指针位置
        }
        else
        {
            Read_size_a = read_Length ;
            memcpy(output_addr, rb_handle->array_addr + rb_handle->head, Read_size_a);
            rb_handle->Length -= read_Length ;//记录剩余数据量
            rb_handle->head += Read_size_a ;//重新定位头指针位置
            if(rb_handle->head == rb_handle->max_Length)
                rb_handle->head = 0 ;//如果读取数据后头指针刚好写到数组尾部,则回到开头,防止越位
        }
        return RING_BUFFER_SUCCESS ;
    }
}

/**
 * \brief 获取缓冲区里已储存的数据长度
 * \param[in] rb_handle: 缓冲区结构体句柄
 * \return 返回缓冲区里已储存的数据长度
*/
uint32_t RB_Get_Length(ring_buffer *rb_handle)
{
    return rb_handle->Length ;
}

/**
 * \brief 获取缓冲区可用储存空间
 * \param[in] rb_handle: 缓冲区结构体句柄
 * \return 返回缓冲区可用储存空间
*/
uint32_t RB_Get_FreeSize(ring_buffer *rb_handle)
{
    return (rb_handle->max_Length - rb_handle->Length) ;
}

main.c和makefile文件见前文。

  • 14
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

汽车电子嵌入式

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

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

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

打赏作者

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

抵扣说明:

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

余额充值