一,什么是 ringbuff
ringbuff: 翻译成中文就是环形缓冲区。
网上关于 ringbuff
的介绍已经非常多了,我也分享一下我对 ringbuff
的认识。ringbuff
可以理解为学校操场上的跑道,ringbuff
初始化就是新建了一个 400
的环形操场跑道,跑道上有 2 名同学 (write
和 read
),每次写入一个数据,write
往前跑一步,每次读一个数据,read
往前跑一步,read
永远不可以超过 write
。刚开始起点都是 0
,如果 write
写的速度慢了,被 read
追上了,那么 ringbuff
缓存数据就为空。 如果 write
写的速度快了,read
读慢了,被 write
跑了一圈又追上了,那么 ringbuff
缓存区就是满。
这里借用一下他人的图,原图地址 , 侵删。
二. 怎么使用 ringbuff
在知道怎么使用 ringbuff
前先要看一下,ringbuff
的结构体:
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;
};
*buffer_ptr : 缓冲区指针
read_mirror :读取镜像。可以理解为一张白纸,读完了就翻过一页再读
read_index :读数据的位置
write_mirror : 写入镜像。可以理解为一张白纸,写完了就翻过一页再写
write_index : 写入数据的位置
buffer_size : 缓存区大小
这里有一个 C 语言 的小知识点:位域 。 这里使用了一个 u16
存储了 read_mirror
和 read_index
, 所以 index
最大只能到 32767 。这个大小满足绝大数的场景。
1 . 初始化 ringbuff
void rt_ringbuffer_init(struct rt_ringbuffer *rb,
rt_uint8_t *pool,
rt_int16_t size)
*rb : 环形缓冲区句柄
*pool : 缓存区指针
size : 缓存区大小
2 . 往 ringbuff 写入数据
rt_size_t rt_ringbuffer_put(struct rt_ringbuffer *rb,
const rt_uint8_t *ptr,
rt_uint16_t length)
*rb : 环形缓冲区句柄
*ptr : 写入数据的指针
length : 写入数据的长度
3 . 强制往 ringbuff 写入数据
rt_size_t rt_ringbuffer_put_force(struct rt_ringbuffer *rb,
const rt_uint8_t *ptr,
rt_uint16_t length)
*rb : 环形缓冲区句柄
*ptr : 写入数据的指针
length : 写入数据的长度
强制写数据使用场景:ringbuff
缓存区中的数据已经满了,但是还要写入数据。这种情况将会导致前面写入的数据被覆盖,导致数据丢失,请谨慎使用。
4. 从 ringbuff 读取数据
rt_size_t rt_ringbuffer_get(struct rt_ringbuffer *rb,
rt_uint8_t *ptr,
rt_uint16_t length)
*rb : 环形缓冲区句柄
*ptr : 读数据的存放的位置
length : 读取的长度
5. 往 ringbuff 写入一个字节的数据
rt_size_t rt_ringbuffer_putchar(struct rt_ringbuffer *rb, const rt_uint8_t ch)
*rb : 环形缓冲区句柄
ch : 将要写入的数据
6. 强制往 ringbuff 写入一个字节的数据
rt_size_t rt_ringbuffer_putchar_force(struct rt_ringbuffer *rb, const rt_uint8_t ch)
*rb : 环形缓冲区句柄
ch : 将要写入的数据
注意强制写入将会导致数据被覆盖。
7. 从 ringbuff 读取一个字节的数据
rt_size_t rt_ringbuffer_getchar(struct rt_ringbuffer *rb, rt_uint8_t *ch)
*rb : 环形缓冲区句柄
*ch : 存储读取的一个字节的变量
8. 获取 ringbuff 中已经使用的控件
rt_size_t rt_ringbuffer_data_len(struct rt_ringbuffer *rb)
*rb : 环形缓冲区句柄
9. 复位 ringbuff
void rt_ringbuffer_reset(struct rt_ringbuffer *rb)
*rb : 环形缓冲区句柄
10. 创建 ringbuff
struct rt_ringbuffer* rt_ringbuffer_create(rt_uint16_t size)
size : 唤醒缓冲区的大小
rt_ringbuffer_create
在函数内部实现了 pool
然后调用了 rt_ringbuffer_init
11. 销毁 ringbuff
void rt_ringbuffer_destroy(struct rt_ringbuffer *rb)
这个只能销毁调用 rt_ringbuffer_create
创建的 ring_buff
12 . 获取 ringbuff 的大小
rt_inline rt_uint16_t rt_ringbuffer_get_size(struct rt_ringbuffer *rb)
三. 原理分析 ringbuff
1. 初始化 ringbuff
void rt_ringbuffer_init(struct rt_ringbuffer *rb,
rt_uint8_t *pool,
rt_int16_t size)
{
/* initialize read and write index */
rb->read_mirror = rb->read_index = 0;// 初始化时 读下标设置为 0
rb->write_mirror = rb->write_index = 0;// 初始化时 写下标设置为 0
/* set buffer pool and size */
rb->buffer_ptr = pool;// 设置 ring_buff 的缓存区地址
rb->buffer_size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE);// 保证 size 的大小是对齐的
}
2. 往 ringbuff 写入数据
rt_size_t rt_ringbuffer_put(struct rt_ringbuffer *rb,
const rt_uint8_t *ptr,
rt_uint16_t length)
{
rt_uint16_t size;
/* whether has enough space */
size = rt_ringbuffer_space_len(rb);// 获取 ring_buff 中可用空间的大小
/* no space */
if (size == 0)
return 0; // 如果空间不够 直接返回
/* drop some data */
if (size < length) // 如果缓存区的控件不够保存这一次数据, 则把能够写入的这一部分数据写进去
length = size;
if (rb->buffer_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; // 返回写入数据的长度
}
memcpy(&rb->buffer_ptr[rb->write_index],// 把能够写入第一个镜像的数据线写入第一个镜像的缓存区
&ptr[0],
rb->buffer_size - rb->write_index);
memcpy(&rb->buffer_ptr[0],// 前一个镜像写满了,就要从头写了,这里就体现了循环
&ptr[rb->buffer_size - rb->write_index],
length - (rb->buffer_size - rb->write_index));
/* we are going into the other side of the mirror */
rb->write_mirror = ~rb->write_mirror;// 完成 镜像 0 和 1 的翻转
rb->write_index = length - (rb->buffer_size - rb->write_index);// 重新设置写数据的小标
return length;// 返回写入数据的长度
}
3. 强制往 ringbuff 写入数据
rt_size_t rt_ringbuffer_put_force(struct rt_ringbuffer *rb,
const rt_uint8_t *ptr,
rt_uint16_t length)
{
rt_uint16_t space_length;
space_length = rt_ringbuffer_space_len(rb); // 获取可用缓存区大小
if (length > rb->buffer_size) // 如果长度超过了当前 ring_buff 的最大值
{
ptr = &ptr[length - rb->buffer_size];// 获取超出部分的地址
length = rb->buffer_size;// lenght 设置为 ring_buff 的大小
}
if (rb->buffer_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;// 移动 write index
if (length > space_length) // 如果长度大于可用缓存区大小
rb->read_index = rb->write_index;// 因为数据已经被覆盖掉了,所以 read 的 index 必须往前移动
return length; // 返回写入数据的长度
}
memcpy(&rb->buffer_ptr[rb->write_index],// 拷贝数据在当前镜像
&ptr[0],
rb->buffer_size - rb->write_index);
memcpy(&rb->buffer_ptr[0],// 拷贝数据到下一个镜像
&ptr[rb->buffer_size - rb->write_index],
length - (rb->buffer_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->buffer_size - rb->write_index); // 写完数据后移动 write index
if (length > space_length) // 如果长度大于可用空间
{
rb->read_mirror = ~rb->read_mirror; // 翻转读镜像
rb->read_index = rb->write_index; // 移动 read_index 到 write_index
}
return length;
}
4. 从 ringbuff 获取数据
rt_size_t rt_ringbuffer_get(struct rt_ringbuffer *rb,
rt_uint8_t *ptr,
rt_uint16_t length)
{
rt_size_t size;
/* whether has enough data */
size = rt_ringbuffer_data_len(rb);// 检查当前 ringbuff 中是否有足够的空间
/* no data */
if (size == 0)
return 0; // 如果没有数据则直接返回
/* less data */
if (size < length)
length = size;// 如果已经存在的数据小于想要获取的长度,那么就会把长度设置为 size
if (rb->buffer_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->buffer_size - rb->read_index);// 拷贝当前镜像的数据
memcpy(&ptr[rb->buffer_size - rb->read_index],
&rb->buffer_ptr[0],
length - (rb->buffer_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->buffer_size - rb->read_index);// 移动读下标
return length;
}
5. 往 ringbuff 中写入一个字符
rt_size_t rt_ringbuffer_putchar(struct rt_ringbuffer *rb, const rt_uint8_t ch)
{
/* whether has enough space */
if (!rt_ringbuffer_space_len(rb)) // 没有足够的空间就直接返回了
return 0;
rb->buffer_ptr[rb->write_index] = ch;// 把这个字符写入到缓冲区的指定位置
/* flip mirror */
if (rb->write_index == rb->buffer_size-1)// 检查写入这个字符后,当前镜像是否写满
{
rb->write_mirror = ~rb->write_mirror;// 翻转镜像
rb->write_index = 0;// 设置下标为0
}
else
{
rb->write_index++; // 下标加1
}
return 1; // 写入一个字符,返回 1
}
6. 往 ringbuff 强制写入一个字符
rt_size_t rt_ringbuffer_putchar_force(struct rt_ringbuffer *rb, const rt_uint8_t ch)
{
enum rt_ringbuffer_state old_state;
old_state = rt_ringbuffer_status(rb);// 获取状态
rb->buffer_ptr[rb->write_index] = ch;// 写入数据
/* flip mirror */
if (rb->write_index == rb->buffer_size-1) // 检查当前镜像是不是满了
{
rb->write_mirror = ~rb->write_mirror; // 翻转写镜像
rb->write_index = 0;// 翻转之后设置下标为 0
if (old_state == RT_RINGBUFFER_FULL) // 如果 ringbuff 的状态是满
{
rb->read_mirror = ~rb->read_mirror; // 翻转读镜像
rb->read_index = rb->write_index; // 设置读下标和写下标一致
}
}
else
{
rb->write_index++; // 写下标加1
if (old_state == RT_RINGBUFFER_FULL)
rb->read_index = rb->write_index;// 如果满,设置读下标等于写下标
}
return 1; // 写入一个字符,返回1
}
7. 从 ringbuff 获取一个字符
rt_size_t rt_ringbuffer_getchar(struct rt_ringbuffer *rb, rt_uint8_t *ch)
{
/* ringbuffer is empty */
if (!rt_ringbuffer_data_len(rb)) // 检查 ringbuff 是否为空
return 0;
/* put character */
*ch = rb->buffer_ptr[rb->read_index];// 获取当前读下标的数据
if (rb->read_index == rb->buffer_size-1)// 如果当前镜像满了
{
rb->read_mirror = ~rb->read_mirror;// 翻转镜像
rb->read_index = 0; // 设置读数据的下标为0
}
else
{
rb->read_index++; // 下标加1
}
return 1;// 读取一个字节,返回1
}
8. 获取 ringbuff 中数据的长度
rt_size_t rt_ringbuffer_data_len(struct rt_ringbuffer *rb)
{
switch (rt_ringbuffer_status(rb)) // 获取 ringbuff 的状态
{
case RT_RINGBUFFER_EMPTY:
return 0; // 空就返回 0
case RT_RINGBUFFER_FULL:
return rb->buffer_size; // 满就返回缓冲区的大小
case RT_RINGBUFFER_HALFFULL:// 半满
default:
if (rb->write_index > rb->read_index) // 如果在同一镜像
return rb->write_index - rb->read_index;// 返回下标差
else
return rb->buffer_size - (rb->read_index - rb->write_index);// 如果不在同一镜像,通过计算获取数据的长度
};
}
9. 重置 ringbuff
void rt_ringbuffer_reset(struct rt_ringbuffer *rb)
{
rb->read_mirror = 0;
rb->read_index = 0;
rb->write_mirror = 0;
rb->write_index = 0;
} // 所有的值都设置为 0
10. 创建一个 ringbuff
struct rt_ringbuffer* rt_ringbuffer_create(rt_uint16_t size)
{
struct rt_ringbuffer *rb;
rt_uint8_t *pool;
size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE);// 大小做字节对齐
rb = (struct rt_ringbuffer *)rt_malloc(sizeof(struct rt_ringbuffer));// 申请内存
if (rb == RT_NULL)
goto exit;
pool = (rt_uint8_t *)rt_malloc(size);// 申请数据缓冲区内存
if (pool == RT_NULL)
{
rt_free(rb);
rb = RT_NULL;
goto exit;
}
rt_ringbuffer_init(rb, pool, size);// 初始化 ringff
exit:
return rb;
}
这个 API
简化了使用 ringbuff
所需要的 形参。本质还是调用了 rt_ringbuffer_init
11. 摧毁 ringbuff
void rt_ringbuffer_destroy(struct rt_ringbuffer *rb)
{
rt_free(rb->buffer_ptr);
rt_free(rb);// 释放申请的内存
}
四. 总结
ringbuff
读写数据互不干扰,比普通的buff
使用更加灵活。ringbuff
的使用场景非常多,可以解决读写速度不一致的问题,RT-Thread 的组件框架中也使用到了,RT-Thread 已经提供了就不需要我们自己再重复造轮子了,这部分代码也很少,想要数量掌握,还是建议读一下源码。