说明
- RingBuffer(环形缓冲区)也叫做圆形队列(circular queue),使用场景比较广泛,例如:Linux内核中网络数据包的缓存,系统日志的存储等多处使用,以及被广泛的应用于异步通信以及嵌入式设备中,提供高效的数据缓存读写操作。
特征
- 环形缓冲区体现了Ringbuffer是缓冲区设计的一种。
- 圆形队列(circular queue)体现了其数据操作满足FIFO(先进先出)原则。
优点
- 内存循环使用,固定大小的内存区域,不需要申请和释放内存,内存重复使用。
缺点
- 需要进行两次内存拷贝,第一次从外部拷贝到Ringbuffer,第二次从Ringbuffer拷贝到外部使用,对于大块数据,性能影响明显。
原理
- 环形缓冲区通常有一个读指针和一个写指针,发生读写操作时,读写指针跟随操作进行移动,读指针指向下一次读取数据的内存地址,写指针指向下一次写数据的内存地址。
- 当读写指针发生越界时,需要将读写指针重置,以便内存成环,常见有以下两种做法:
- 单字节,读写指针根据buffer size 取模。
- 多字节,读写指针根据buffer size 判断,超过size置为0。
- 注意:环是逻辑上的描述,在实际内存空间中是线性的。
关键问题
- 实际线性内存空间中,读指针在前,写指针在后。
- 实际线性内存空间中,读写指针指向同一个位置。
- 实际线性内存空间中,写指针在前,读指针在后。
- 第二种情况,当读写指针指向同一位置时,此时空间是满还是空?实际情况下空和满都有可能,Ringbuffer还未使用或者读操作追上写操作时,读写指针指向同一位置,此时为空,Ringbuffer写操作追上读操作时,此时为满。
解决办法
- 保持一个存储单元为空
- 写数据时,Ringbuffer空闲空间等于实际空闲空间减一,以保持至少一个存储单元为空,这样就避免了出现读指针被写指针追上的情况(即:缓冲区满的情况);读数据不做限制。
- 这种处理方法,如果读写指针指向同一位置,则缓冲区为空;如果写指针位于读指针的相邻后一个位置,则缓冲区为满。
- 使用变量计数
- 定义一个变量来保存数据长度,写操作时,变量加上已写数据长度,读操作时,变量减去已读数据长度,通过该变量即可获知空闲空间。
- 镜像逻辑地址法
- 在国产物联网操作系统RT-thread中使用了一种非常特殊的方式来表示队满还是空。
镜像逻辑地址法
- 这篇Blog可以借鉴,但是原理层面感觉没讲清楚。
- 解释:
- 镜像:在逻辑层面将初始位置的读写指针认为是处于镜像的正面,当指针发生翻转(重置为0)时认为指针进入镜像的反面,再翻转回来认为处于镜像的正面,类似于这样循环。
- 当读写指针处于同一镜像时,说明读写指针翻转次数是一样的(buffer size牵扯读写指针导致的),只会出现写指针在读指针前面和读写指针位置一样的情况,读写指针位置一致则说明:空间为空。
- 当读写指针处于不同镜像时,说明读写指针翻转次数相差一次,只会出现读指针在写指针前面和读写指针位置一样的情况,读写指针位置一致则说明:空间为满。
- 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;
};