【C】ringbuffer的C语言实现

最近在学习Android 下的Bluedroid时, 看到在Bluedroid中实现了ringbuffer这一数据结构, 比较简洁, 所以独立出来进行分享.
Bluedorid官方源码路径
本文分享的ringbuffer例子源码路径

什么是ringbuffer?

在这里插入图片描述
上述图片是来自Amazon AVS C++ SDK中的图片. Shared Memory Ring Buffer.

如上图所示, ringbuffer 实质上就是一个连续内存/首位相连的一个环形缓冲区. 这里数据结构在音频数据的存储上比较常用到.
本文所分享的ringbuffer比较简单,同时仅支持一个写一个读. 对于event的存储我们通常会使用链表这一数据结构,而对于stream数据流我们通常会采用ringbuffer这一个数据结构.

具体实现数据数据
头文件的定义
#pragma once

#include <stdint.h>
#include <stddef.h>

typedef struct ringbuffer_t ringbuffer_t;

// NOTE:
// None of the functions below are thread safe when it comes to accessing the
// *rb pointer. It is *NOT* possible to insert and pop/delete at the same time.
// Callers must protect the *rb pointer separately.

// Create a ringbuffer with the specified size
// Returns NULL if memory allocation failed. Resulting pointer must be freed
// using |ringbuffer_free|.
ringbuffer_t* ringbuffer_init(const size_t size);


// Frees the ringbuffer structure and buffer
// Save to call with NULL.
void ringbuffer_free(ringbuffer_t* rb);

// Returns remaining buffer size
size_t ringbuffer_available(const ringbuffer_t* rb);


// Returns size of data in buffer
size_t ringbuffer_size(const ringbuffer_t* rb);

// Attempts to insert up to |length| bytes of data at |p| into the buffer
// Return actual number of bytes added. Can be less than |length| if buffer
// is full.
size_t ringbuffer_insert(ringbuffer_t* rb, const uint8_t* p, size_t length);

// Peek |length| number of bytes from the ringbuffer, starting at |offset|,
// into the buffer |p|. Return the actual number of bytes peeked. Can be less
// than |length| if there is less than |length| data available. |offset| must
// be non-negative.
size_t ringbuffer_peek(const ringbuffer_t* rb, off_t offset, uint8_t* p,
                       size_t length);

// Does the same as |ringbuffer_peek|, but also advances the ring buffer head
size_t ringbuffer_pop(ringbuffer_t* rb, uint8_t* p, size_t length);

// Deletes |length| bytes from the ringbuffer starting from the head
// Return actual number of bytes deleted.
size_t ringbuffer_delete(ringbuffer_t* rb, size_t length);
具体函数和数据结构的的实现
  1. ringbuffer 结构体的定义
struct ringbuffer_t {
  size_t total; //总的数据大小
  size_t available; //剩余可写入的buffer大小
  uint8_t* base; 
  uint8_t* head; //可以理解为读指针 
  uint8_t* tail; //可以理解为写指针
};

在struct ringbuffer_t 中的uint8_t* base是用于解决头尾相连部分的特殊位置处理(类似链表中的哨兵),这在接下来的内容中会说明.

  1. ringbuffer初始化
ringbuffer_t* ringbuffer_init(const size_t size) {
  ringbuffer_t* p =
      static_cast<ringbuffer_t*>(malloc(sizeof(ringbuffer_t)));
      
  p->base = static_cast<uint8_t*>(malloc(size));
  p->head = p->tail = p->base;
  p->total = p->available = size;
  return p;
}

对于ringbuffer_init, 主要是进行内存的分配,和相关变量的初始化.
初始时p->head = p->tail = p->base;

  1. 往ringbuffer中写入数据
size_t ringbuffer_insert(ringbuffer_t* rb, const uint8_t* p, size_t length) {
  assert(rb);
  assert(p);
  if (length > ringbuffer_available(rb)) length = ringbuffer_available(rb);
  for (size_t i = 0; i != length; ++i) {
    *rb->tail++ = *p++;
    if (rb->tail >= (rb->base + rb->total)) rb->tail = rb->base;
  }
  rb->available -= length;
  return length;
}

关键代码说明:

在往rb->tail 中写入数据前,需要先判断当前需要写入的数据和ringbuffer还可以写入的数据大小, 不能覆盖未被读走的数据.
另外一点,
当rb->tail >= (rb->base + rb->total)需要进行临界点的处理, 也就是当写指针>= buffer内存地址的尾部时 需要将rb->tail 赋值为rb->base, 这点可以理解为:虽然我们说ringbuffer时首尾相连的, 但分配的内存的角度并不是首尾连续的,而是需要在代码逻辑上将写指针(rb->tail)移动到尾部时进行了特殊处理,将其赋值为起始的内存地址.

  1. 删除ringbuffer中指定长度的数据
  assert(rb);
  if (length > ringbuffer_size(rb)) length = ringbuffer_size(rb);

  rb->head += length;
  if (rb->head >= (rb->base + rb->total)) rb->head -= rb->total;
  
  rb->available += length;
  return length;
}

关键代码说明:
还是在临界点的处理上, 这里由于是删除数据,不需要进行数据的拷贝,直接将rb->head 移动length, 然后进行临界点的处理,

if (rb->head >= (rb->base + rb->total)) rb->head -= rb->total;

看到这里可能有些同学会对 rb->head -= rb->total;处理会有疑问,怎么不是rb->head = rb->base?? 这是因为我们在前面是直接将 rb->head += length; 也就是rb->head 的值是有可能大于在初始化是分配的最大有效地址值,而对于ringbuffer首尾是相连的,也就是当读指针移动到“分配的最大有效地址值”需要将读指针直接移动rb->head -= rb->total;的位置进行数据的删除.

  1. 取出偏移指定offset的指定长度数据
size_t ringbuffer_peek(const ringbuffer_t* rb, off_t offset, uint8_t* p,
                       size_t length) {
  assert(rb);
  assert(p);
  assert(offset >= 0);
  assert((size_t)offset <= ringbuffer_size(rb));

  uint8_t* b = ((rb->head - rb->base + offset) % rb->total) + rb->base; //得到需要开始读取数据的起始地址
  const size_t bytes_to_copy = (offset + length > ringbuffer_size(rb))
                                   ? ringbuffer_size(rb) - offset
                                   : length; //计算需要copy的数据

  for (size_t copied = 0; copied < bytes_to_copy; ++copied) {
    *p++ = *b++;
    if (b >= (rb->base + rb->total)) b = rb->base; //临界点处理
  }

  return bytes_to_copy;
}

关键代码说明,
在 uint8_t* b = ((rb->head - rb->base + offset) % rb->total) + rb->base;中,rb->head - rb->base的操作得到的是读指针距离rb->base的距离,进而将(rb->head - rb->base + offset) % rb->total 得到的是读到的数据距离rb->base的字节数.

需要注意的是: ringbuffer_peek操作并不会改变ringbuffer中的相关数据, 只是提取了数据

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值