可以看一下这篇我写的博客,了解一下大概:
RingBuffer 环形缓冲区----镜像指示位_呵呵哒( ̄▽ ̄)"的博客-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/132340883?spm=1001.2014.3001.5501
【回顾】缓冲区变满在环形缓冲区(ring buffer)中会实际发生,一般会有两种处理策略:
🐞① 覆盖老数据
🐞② 抛出“异常”
镜像指示位:缓冲区的长度如果是n,逻辑地址空间则为0至n-1;那么,规定n至2n-1为镜像逻辑地址空间。本策略规定读写指针的地址空间为0至2n-1,其中低半部分对应于常规的逻辑地址空间,高半部分对应于镜像逻辑地址空间。当指针值大于等于2n时,使其折返(wrapped)到ptr-2n。使用一位表示写指针或读指针是否进入了虚拟的镜像存储区:置位表示进入,不置位表示没进入还在基本存储区。
在读写指针的值相同情况下,如果二者的指示位相同,说明缓冲区为空;如果二者的指示位不同,说明缓冲区为满。这种方法优点是测试缓冲区满/空很简单;不需要做取余数操作;读写线程可以分别设计专用算法策略,能实现精致的并发控制。缺点是读写指针各需要额外的一位作为指示位。
如果缓冲区长度是2的幂,则本方法可以省略镜像指示位。如果读写指针的值相等,则缓冲区为空;如果读写指针相差n,则缓冲区为满,这可以用条件表达式(写指针==(读指针异或缓冲区长度))来判断。
----(来自百度百科)
一、基本步骤
1.数据结构
typedef struct ringbuffer
{
uint8 *buffer_ptr;
uint16 read_mirror : 1;
uint16 read_index : 15;
uint16 write_mirror : 1;
uint16 write_index : 15;
/* as we use msb of index as mirror bit, the size should be signed and
* could only be positive. */
uint16 size;
}ringbuff;
2.缓冲区初始化
// 缓冲区初始化
void rb_init(ringbuff *rb,uint8 *pool,uint16 size){
/* initialize read and write index */
rb->read_mirror = rb->read_index = 0;
rb->write_mirror = rb->write_index = 0;
/* set buffer pool and size */
rb->buffer_ptr = pool;
rb->size = DATA_ALIGN_DOWN(size, DATA_ALIGN_SIZE);
}
3.创建一个ringbuffer
// 创建一个ringbuff
ringbuff* rb_create(uint16_t size) {
ringbuff *rb;
uint8_t *pool;
size = DATA_ALIGN_DOWN(size, DATA_ALIGN_SIZE);// 大小做字节对齐
rb = (ringbuff *)malloc(sizeof(ringbuff));// 申请内存
if (rb == NULL)
goto exit;
pool = (uint8_t *)malloc(size);// 申请数据缓冲区内存
if (pool == NULL) {
free(rb);
rb = NULL;
goto exit;
}
rb_init(rb, pool, size);// 初始化 ringbuff
exit:
return rb;
}
4.销毁环形缓冲区
// 摧毁 ringbuff
void rb_destroy(ringbuff *rb) {
cout<<"销毁ringbuff~"<<endl;
free(rb->buffer_ptr);
free(rb);// 释放申请的内存
}
二、缓冲区中填充指定数据长度的数据
5. 缓冲区中填充指定数据长度的数据
举个例子:(当缓冲区空间小于待写入的数据长度事覆盖缓冲区原有数据)
图1:当环形缓冲区为空时,读索引和写索引指向相同的位置(这里初始化为0);
图2:写操作:想往(rb->buffer_size = 8)缓冲区中写入15个元素:123456789ABCDEF,但写入的数据长度(length)超过缓冲区空闲长度(space_length)了。解决方法:RT-Thread(覆盖老数据策略)就是只截取后8位数据放入缓冲区。
可知length=15,space_length=8,满足length > space_length,让ptr = &ptr[length - rb->buffer_size]
图3:由图2的操作可以得到以上的环形缓冲区的数据内容分布
图4:读操作,读取缓冲区4个元素:89AB,修改读索引
图5:写操作,写入缓冲区4个元素:1234,修改写索引
图6:读操作,读取缓冲区4个元素:CDEF,修改读索引
🐞① 当缓冲区空间小于待写入的数据长度事覆盖缓冲区原有数据
/* 缓冲区中填充指定数据长度的数据(当缓冲区空间小于待写入的数据长度事覆盖缓冲区原有数据) */
uint16 rb_put_force(ringbuff *rb,const uint8 *ptr,uint16 length);
// 强制往 ringbuff 写入数据
uint16 rb_put_force(ringbuff *rb,const uint8 *ptr,uint16 length) {
uint16 space_length = 0;
space_length = rb_space_len(rb);
// cout<<"ptr: "<<ptr<<endl;
// cout<<"space_length: "<<space_length<<endl;
// cout<<"length: "<<length<<endl;
if (length > space_length) {
ptr = &ptr[length - rb->size];
length = rb->size;
}
// cout<<"ptr: "<<ptr<<endl;
// cout<<"length: "<<length<<endl;
if (rb->size - rb->write_index > length)
{
// cout<<"lailailailai"<<endl;
/* read_index - write_index = empty space */
memcpy(&rb->buffer_ptr[rb->write_index], ptr, length);
/* this should not cause overflow because there is enough space for
* length of data in current mirror */
rb->write_index += length;
if (length > space_length)
rb->read_index = rb->write_index;
return length;
}
memcpy(&rb->buffer_ptr[rb->write_index],
&ptr[0],
rb->size - rb->write_index);
memcpy(&rb->buffer_ptr[0],
&ptr[rb->size - rb->write_index],
length - (rb->size - rb->write_index));
/* we are going into the other side of the mirror */
rb->write_mirror = ~rb->write_mirror;
rb->write_index = length - (rb->size - rb->write_index);
if (length > space_length)
{
rb->read_mirror = ~rb->read_mirror;
rb->read_index = rb->write_index;
}
return length;
}
🐞② 当缓冲区空间小于待写入的数据长度事不覆盖缓冲区原有数据
/* 缓冲区中填充指定数据长度的数据(当缓冲区空间小于待写入的数据长度事不覆盖缓冲区原有数据) */
uint16 rb_put(ringbuff *rb,const uint8 *ptr,uint16 length);
// 往 ringbuff 写入数据
uint16 rb_put(ringbuff *rb,const uint8 *ptr,uint16 length) {
uint16 size = 0;
/* whether has enough space */
size = rb_space_len(rb);// 获取 ring_buff 中可用空间的大小
/* no space */
if (size == 0)
return 0;// 如果空间不够 直接返回
/* drop some data */
if (size < length) // 如果缓存区的空间不够保存这一次数据, 则把能够写入的这一部分数据写进去
length = size;
/* One-time write */
if (rb->size - rb->write_index > length)
{
/* read_index - write_index = empty space */
memcpy(&rb->buffer_ptr[rb->write_index], ptr, length);
/* this should not cause overflow because there is enough space for
* length of data in current mirror */
rb->write_index += length;
return length;// 返回写入数据的长度
}
/* two-time write */
memcpy(&rb->buffer_ptr[rb->write_index],
&ptr[0],
rb->size - rb->write_index);
memcpy(&rb->buffer_ptr[0],
&ptr[rb->size - rb->write_index],
length - (rb->size - rb->write_index));
/* we are going into the other side of the mirror */
rb->write_mirror = ~rb->write_mirror;
rb->write_index = length - (rb->size - rb->write_index);
return length;
}
6. 缓冲区中获取指定长度的数据(返回实际获取数据的长度)
/* 缓冲区中获取指定长度的数据(返回实际获取数据的长度) */
uint16 rb_get(ringbuff *rb,uint8 *ptr,uint16 length);
// 从 ringbuff 获取数据
uint16 rb_get(ringbuff *rb,uint8 *ptr,uint16 length) {
uint16 size = 0;
/* The length of the existing data in the buffer */
size = rb_data_len(rb);
/* no data */
if (size == 0)
return 0;
/* less data */
if (size < length)
length = size;
cout<<"size: "<<size<<" < "<<"length: " << length<<(size < length)<<endl;
if (rb->size - rb->read_index > length)
{
/* copy all of data */
memcpy(ptr, &rb->buffer_ptr[rb->read_index], length);
/* this should not cause overflow because there is enough space for
* length of data in current mirror */
rb->read_index += length;
return length;
}
memcpy(&ptr[0],
&rb->buffer_ptr[rb->read_index],
rb->size - rb->read_index);
memcpy(&ptr[rb->size - rb->read_index],
&rb->buffer_ptr[0],
length - (rb->size - rb->read_index));
/* we are going into the other side of the mirror */
rb->read_mirror = ~rb->read_mirror;
rb->read_index = length - (rb->size - rb->read_index);
return length;
}
7.测试和打印
void readprint(ringbuff* rb,uint8 buff[]) {
cout<<"读取数据:";
int i = 0;
while(buff[i]!='\0') {
cout<<buff[i++];
}
print(rb);
}
void writeprint(ringbuff* rb){
for(int i=0;i<rb->size;i++){
cout<<rb->buffer_ptr[i];
}
print(rb);
}
void test01() {
ringbuff* rb = rb_create(9);
const uint8 p[] = "123456789ABCDEF";
uint32_t len = sizeof(p) / sizeof(char);
cout<<"写入数据:"<<p<<endl;
// rb_put(rb,p,len-1);
rb_put_force(rb,p,len-1);
writeprint(rb); // 89ABCDEF
uint8 saveBuff[20] = "";
rb_get(rb,saveBuff,4); // 89AB
readprint(rb,saveBuff);
const uint8 p1[] = "1234";
cout<<"写入数据:"<<p1<<endl;
rb_put_force(rb,p1,4); // 1234CDEF
writeprint(rb);
memset(saveBuff,0,20);
rb_get(rb,saveBuff,4); // CDEF
cout<<"读取数据:";
readprint(rb,saveBuff);
// 销毁ringbuff
rb_destroy(rb);
}
写入数据:123456789ABCDEF
89ABCDEF
rb->write_index: 0
rb->read_index: 0
rb->write_mirror: 1
rb->read_mirror: 0
rb_data_len: 8
rb_space_len: 0
size: 8 < length: 40
读取数据:89AB
rb->write_index: 0
rb->read_index: 4
rb->write_mirror: 1
rb->read_mirror: 0
rb_data_len: 4
rb_space_len: 4
写入数据:1234
1234CDEF
rb->write_index: 4
rb->read_index: 4
rb->write_mirror: 1
rb->read_mirror: 0
rb_data_len: 8
rb_space_len: 0
size: 8 < length: 40
读取数据:读取数据:CDEF
rb->write_index: 4
rb->read_index: 0
rb->write_mirror: 1
rb->read_mirror: 1
rb_data_len: 4
rb_space_len: 4
销毁ringbuff~
三、缓冲区中填充一个数据
5. 缓冲区中填充一个数据
举个例子:(当缓冲区空间小于待写入的数据长度事覆盖缓冲区原有数据)
图1:依次存入1、2、3、4、5、6、7、8、A、B、C、D、E、F这些字符,直到缓冲区为满
图2:依次读出单个字符: 8、9、A、B
🐞① 当缓冲区空间小于待写入的数据长度事覆盖缓冲区原有数据
/* 缓冲区中填充一个数据(当缓冲区空间小于待写入的数据长度事覆盖缓冲区原有数据) */
uint16 rb_putchar_force(ringbuff *rb, const uint8 ch);
// 往 ringbuff 强制写入一个字符
uint16 rb_putchar_force(ringbuff *rb, const uint8 ch) {
enum rb_state old_state = rb_status(rb);// 获取状态
rb->buffer_ptr[rb->write_index] = ch;// 写入数据
/* flip mirror */
if (rb->write_index == rb->size-1) {// 检查当前镜像是不是满了
rb->write_mirror = ~rb->write_mirror; // 翻转写镜像
rb->write_index = 0;// 翻转之后设置下标为 0
if (old_state == RINGBUFFER_FULL) {// 如果 ringbuff 的状态是满
rb->read_mirror = ~rb->read_mirror; // 翻转读镜像
rb->read_index = rb->write_index; // 设置读下标和写下标一致
}
}else{
rb->write_index++; // 写下标加1
if (old_state == RINGBUFFER_FULL)
rb->read_index = rb->write_index;// 如果满,设置读下标等于写下标
}
return 1; // 写入一个字符,返回1
}
🐞② 当缓冲区空间小于待写入的数据长度事不覆盖缓冲区原有数据
/* 缓冲区中填充一个数据(当缓冲区空间小于待写入的数据长度事不覆盖缓冲区原有数据) */
uint16 rb_putchar(ringbuff *rb, const uint8 ch);
// 往 ringbuff 中写入一个字符
uint16 rb_putchar(ringbuff *rb, const uint8 ch) {
/* whether has enough space */
if (!rb_space_len(rb)) // 没有足够的空间就直接返回了
return 0;
rb->buffer_ptr[rb->write_index] = ch;// 把这个字符写入到缓冲区的指定位置
/* flip mirror */
if (rb->write_index == rb->size-1) {// 检查写入这个字符后,当前镜像是否写满
rb->write_mirror = ~rb->write_mirror;// 翻转镜像
rb->write_index = 0;// 设置下标为0
}else{
rb->write_index++; // 下标加1
}
return 1;// 写入一个字符,返回 1
}
6. 缓冲区中获取一个数据(返回实际获取数据的长度)
/* 缓冲区中获取一个数据(返回实际获取数据的长度) */
uint16 rb_getchar(ringbuff *rb, uint8 *ch);
// 从ringbuff 获取一个字符
uint16 rb_getchar(ringbuff *rb,uint8 *ch) {
/* ringbuffer is empty */
if (!rb_data_len(rb)) // 检查 ringbuff 是否为空
return 0;
/* put character */
*ch = rb->buffer_ptr[rb->read_index];// 获取当前读下标的数据
if (rb->read_index == rb->size-1) {// 如果当前镜像满了
rb->read_mirror = ~rb->read_mirror;// 翻转镜像
rb->read_index = 0; // 设置读数据的下标为0
} else {
rb->read_index++; // 下标加1
}
return 1;// 读取一个字节,返回1
}
7.测试和打印
#include "rb.h"
#include "rb.cpp"
void print(ringbuff *rb) {
cout<<endl;
cout<<"rb->write_index: "<<rb->write_index<<endl;
cout<<"rb->read_index: "<<rb->read_index<<endl;
cout<<"rb->write_mirror: "<<rb->write_mirror<<endl;
cout<<"rb->read_mirror: "<<rb->read_mirror<<endl;
cout<<"rb_data_len: "<<rb_data_len(rb)<<endl;
cout<<"rb_space_len: "<<rb_space_len(rb)<<endl;
cout<<endl;
}
void writeprint(ringbuff* rb){
for(int i=0;i<rb->size;i++){
cout<<rb->buffer_ptr[i];
}
print(rb);
}
void test02() {
ringbuff* rb = rb_create(9);
cout<<"rb->size: "<<rb->size<<endl;
const uint8 p[] = "123456789ABCDEF";
uint32_t len = sizeof(p) / sizeof(char);
// cout<<len<<endl;
cout<<"写入数据:"<<p<<endl;
for(int i=0;i<len-1;i++) {
// rb_putchar(rb,p[i]);
rb_putchar_force(rb,p[i]);
}
writeprint(rb); // 9ABCDEF8
uint8 singlechar = ' ';
for(int i=0;i<4;i++) {
rb_getchar(rb,&singlechar);
cout<<"读单个字符: "<<singlechar<<endl;
}
print(rb);
// 销毁ringbuff
rb_destroy(rb);
}
rb->size: 8
写入数据:123456789ABCDEF
9ABCDEF8
rb->write_index: 7
rb->read_index: 7
rb->write_mirror: 1
rb->read_mirror: 0
rb_data_len: 8
rb_space_len: 0
读单个字符: 8
读单个字符: 9
读单个字符: A
读单个字符: B
rb->write_index: 7
rb->read_index: 3
rb->write_mirror: 1
rb->read_mirror: 1
rb_data_len: 4
rb_space_len: 4
销毁ringbuff~
四、RT-Thread🦥小结
来自此文总结:ring buffer,一篇文章讲透它? - 知乎 (zhihu.com)
🦝 在多线程中,对同一个环形缓冲区进行读写操作时,需要加上锁,不然存在访问不安全问题;
🦝 当只有一个读线程和一个写线程时,用rb_put和rb_get进行读写操作缓冲区是线程安全的,无需加锁;但是rb_put_force不行,因为其可能对读写索引都进行操作的场景,这个时候再进行rb_get读操作,就是不安全访问;
🦝 读写指针已经在读写(rb_get和rb_put)过程中转换为了读写索引。所以read_index(读索引)和write_index(写索引)可以直接用来操作缓冲区,无需转换;
🦝 read_index 和write_index 的大小区间为[0,n−1],n为缓冲区大小;
🦝 RT-Thread的环形缓冲区不需要buffer大小为2的幂。