数据结构 - RingBuffer

说明

  • RingBuffer(环形缓冲区)也叫做圆形队列(circular queue),使用场景比较广泛,例如:Linux内核中网络数据包的缓存,系统日志的存储等多处使用,以及被广泛的应用于异步通信以及嵌入式设备中,提供高效的数据缓存读写操作。

特征

  1. 环形缓冲区体现了Ringbuffer是缓冲区设计的一种。
  2. 圆形队列(circular queue)体现了其数据操作满足FIFO(先进先出)原则。

优点

  1. 内存循环使用,固定大小的内存区域,不需要申请和释放内存,内存重复使用。

缺点

  1. 需要进行两次内存拷贝,第一次从外部拷贝到Ringbuffer,第二次从Ringbuffer拷贝到外部使用,对于大块数据,性能影响明显。

原理

  • 环形缓冲区通常有一个读指针和一个写指针,发生读写操作时,读写指针跟随操作进行移动,读指针指向下一次读取数据的内存地址,写指针指向下一次写数据的内存地址。
  • 当读写指针发生越界时,需要将读写指针重置,以便内存成环,常见有以下两种做法:
  1. 单字节,读写指针根据buffer size 取模。
  2. 多字节,读写指针根据buffer size 判断,超过size置为0。
  • 注意:环是逻辑上的描述,在实际内存空间中是线性的。

关键问题

  • 当读写指针移动时,可能出现三种情况:
  1. 实际线性内存空间中,读指针在前,写指针在后。
  2. 实际线性内存空间中,读写指针指向同一个位置。
  3. 实际线性内存空间中,写指针在前,读指针在后。
  • 第二种情况,当读写指针指向同一位置时,此时空间是满还是空?实际情况下空和满都有可能,Ringbuffer还未使用或者读操作追上写操作时,读写指针指向同一位置,此时为空,Ringbuffer写操作追上读操作时,此时为满。

解决办法

  1. 保持一个存储单元为空
  • 写数据时,Ringbuffer空闲空间等于实际空闲空间减一,以保持至少一个存储单元为空,这样就避免了出现读指针被写指针追上的情况(即:缓冲区满的情况);读数据不做限制。
  • 这种处理方法,如果读写指针指向同一位置,则缓冲区为空;如果写指针位于读指针的相邻后一个位置,则缓冲区为满。
  1. 使用变量计数
  • 定义一个变量来保存数据长度,写操作时,变量加上已写数据长度,读操作时,变量减去已读数据长度,通过该变量即可获知空闲空间。
  1. 镜像逻辑地址法
  • 在国产物联网操作系统RT-thread中使用了一种非常特殊的方式来表示队满还是空。

镜像逻辑地址法

  • 这篇Blog可以借鉴,但是原理层面感觉没讲清楚。
  • 解释:
  1. 镜像:在逻辑层面将初始位置的读写指针认为是处于镜像的正面,当指针发生翻转(重置为0)时认为指针进入镜像的反面,再翻转回来认为处于镜像的正面,类似于这样循环。
  2. 当读写指针处于同一镜像时,说明读写指针翻转次数是一样的(buffer size牵扯读写指针导致的),只会出现写指针在读指针前面和读写指针位置一样的情况,读写指针位置一致则说明:空间为空。
  3. 当读写指针处于不同镜像时,说明读写指针翻转次数相差一次,只会出现读指针在写指针前面和读写指针位置一样的情况,读写指针位置一致则说明:空间为满。
  • RT-Thread中定义的 rt_ringbuffer 结构体,如下:
struct rt_ringbuffer
{
    rt_uint8_t *buffer_ptr;
    rt_uint16_t read_mirror : 1;  //表示读指针的镜像正反
    rt_uint16_t read_index : 15;
    rt_uint16_t write_mirror : 1; //表示写指针的镜像正反
    rt_uint16_t write_index : 15;
    rt_int16_t buffer_size;
};
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值