理解RT-Thead中使用的环形缓冲区 — Buffer Ring
在RTT编程指南(V0.3.0)的P72页“信号量”一节提到了环形缓冲区(Buffer Ring)的使用。因为说是环形,导致一开始总是在脑海里构建一个圆环样的示意图,结果怎么也搞不明白,最后通过官方人士的解释和查了下资料终于搞明白了,这里记录一下。
环形Buffer的特点:通常包含一个读指针(read_index)和一个写指针(write_index)。读指针指向环形
Buffer中第一个可读的数据,写指针指向环形Buffer中第一个可写的缓冲区。通过移动读指针和写指针就可以实现Buffer的数据读取和写入。在
通常情况下,环形Buffer的读用户仅仅会影响读指针,而写用户也仅仅会影响写指针。
环形Buffer的原理:首先在内存里开辟一片区域(大小为
buffer_size),对于写用户,顺次往Buffer里写入东西,一直写到最后那个内存(buffer_size)时再将写指针指向内存区域的首地
址,即接下来的数据转个环放到最开始处,只有遇到Buffer里的有效存储空间为0时,才丢掉数据;对于读用户,顺次从Buffer里读出东西,一直写到
最后那个内存(buffer_size)时再将读指针指向内存区域的首地址,即接下来转个环从最开始处取数据。
有效存储空间与buffer_size的区别:
有效存储空间是指那些没有存放数据,或者以前存放过但已经处理过的数据,就是可用的空间大小;而buffer_size指的是总大小。
通过上面介绍可知,环形Buffer仍然是一长条区域,只不过其空间会被循环使用而已。示意图如下,根据读写指针的位置可分为两种情况,其中阴影填充部分为数据/已用空间,空白的为可用空间,即有效存储空间。
![](http://images0.cnblogs.com/blog/502044/201303/08094237-bcdfb6a555644e81be9fb1e331891cdd.jpg)
下面以编程指南的P72页上的部分示例代码为例:
首先定义一个环形Buffer结构:
struct rb
{
rt_uint16_t read_index, write_index; //读指针和写指针
rt_uint8_t *buffer_ptr; //环形buffer指针
rt_uint16_t buffer_size; //环形buffer大小
};
然后就是实现读/写操作了,对于写操作:
/* 向环形buffer中写入数据 */
static rt_bool_t rb_put(struct rb* rb, const rt uint8_t *ptr, rt_uint16_t length)
{
rt_size_t size;
/* 判断是否有足够的剩余空间 */
//图示的第一种情况,相减即为有效存储空间,就是中间那段空白区域了
if (rb->read_index > rb->write_index)
size = rb->read_index - rb->write_index;
else // 图示的第二种情况,两段效存储空间之和
size = rb->buffer_size - rb->write_index + rb->read_index;
/* 没有多余的空间,即空间不足无法完成写入,直接返回错误 */
if (size < length) return RT_FALSE;
/* 以下均是指有效存储空间满足,可以完成写操作 */
//图示的第一种情况,所以直接将数据从write_index处写入到buffer_ptr中即可
if (rb->read_index > rb->write_index)
{
/* read_index - write_index 即为总的空余空间
* 关于memcpy, 指的是将prt中长度为length的数据拷贝到第一个参数所指的地址
* 其实每次写入都必须是从write_index(写指针)开始,
* 因为write_index是指向环形Buffer中的第一个可写缓冲区
*/
memcpy(&rb->buffer_ptr[rb->write_index], ptr, length);
rb->write_index += length; //写操作完成后需要将写指针后移相应的长度
}
else //图示的第二种情况,有两段可用空间
{
//如果后半段空闲空间足够容纳,则直接拷贝即可
if (rb->buffer_size - rb->write_index > length)
{
memcpy(&rb->buffer_ptr[rb->write_index], ptr, length);
rb->write_index += length; //写指针后移相应的长度
}
else //如果后半段空间不足,则先将后半段装满,然后再将剩余的装到前半段之中
{
/*
* write_index 后面剩余的空间不存在足够的长度,
* 需要把部分数据复制到前面的剩余空间中
*/
//先塞满后半段,注意大小不是length,而是后半段的长度
memcpy(&rb->buffer_ptr[rb->write index], ptr, rb->buffer_size - rb->write_index);
//此时只能从buffer_ptr的开始处进行写入了,长度为总长减去上次已经写入的
memcpy(&rb->buffer_ptr[0], &ptr[rb->buffer_size - rb->write_index], length - (rb->buffer_size - rb->write_index));
//写指针后移相应的长度,其实就是第二次写入的长度
rb->write_index = length - (rb->buffer_size - rb->write_index);
}
}
return RT_TRUE;
}
对于读操作:
/* 从环形buffer中读出数据 */
static rt_bool_t rb_get(struct rb* rb, rt_uint8 t *ptr, rt_uint16_t length)
{
rt_size_t size;
/* 判断是否有足够的数据 */
if (rb->read_index > rb->write_index) //图示的第一种情况,有效数据为前后两段的和
size = rb->buffer_size - rb->read_index + rb->write_index;
else // 图示的第二种情况, 中间一段为数据,直接相减即可
size = rb->write_index - rb->read_index;
/* 没有足够的数据,如果剩余数据不足,这直接返回错误 */
if (size < length) return RT_FALSE;
/* 以下均是指剩余数据量满足,可以完成读操作 */
if (rb->read_index > rb->write_index) //图示的第一种情况,有两段数据
{
//如果后半段数据足够,则直接取用
if (rb->buffer_size - rb->read_index > length)
{
//这里是将buffer_ptr里面取出长度为length的数据放到ptr之中;
//切记总是从read_index(读指针)开始取
memcpy(ptr, &rb->buffer_ptr[rb->read_index], length);
rb->read_index += length; //读指针后移相应的长度
}
else //如果后半段不够用,则先取完后半段,然后从前半段取剩余的部分
{
/* read index的数据不够,需要分段复制 */
//注意数据长度
memcpy(ptr, &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);
rb->read_index = length - rb->buffer_size + rb->read_index;
}
}
else //图示的第二种情况,仅中间一段有数据,直接取用
{
memcpy(ptr, &rb->buffer_ptr[rb->read_index], length);
rb->read_index += length; //读指针后移相应的长度
}
return RT_TRUE;
}