当前位置: 首页 > news >正文

环形缓冲区 ring buffer 概述

环形缓冲区 ring buffer 概述

1. 简介

环形缓冲区(ring buffer),是一种用于表示一个固定尺寸、头尾相连的缓冲区的数据结构,适合缓存数据流。也称作环形缓冲区(circular buffer),环形队列(circular queue),循环缓冲区(cyclic buffer)。

2. 特点及应用场景

环形缓冲区的特点:

  • 固定大小
  • 线性地址空间
  • 先进先出FIFO
  • 高效读取(数据消费后不需要移动其他数据)

环形缓冲区的应用场景:

  • 网络通信(TCP/IP网络协议栈数据缓存)
  • 任务间的通信(生产者消费者缓存、异步通信优化)
  • 操作系统内核(串口网口等驱动设备数据缓存、log日志缓存)
  • 音频/视频流处理(流媒体传输、实时编解码)

环形缓冲区的典型实现:

  • Linux内核kfifo:采用镜像指示位法实现无锁环形队列,缓冲区大小为2的幂
  • RT-Thread的ringbuffer:为嵌入式系统优化,支持零拷贝操作和中断安全API
  • Boost库circular_buffer:支持动态扩容和元素保留策略,提供STL兼容接口

3. 设计要点

3.1 如何在线性地址空间上实现循环读写?

所谓循环读写是指在环形缓冲区的末尾读写之后,下一次读写需要回到环形缓冲区首地址的过程。也称作指针回绕。

对于环形缓冲区首地址m_buffer和缓冲区大小bufferSize是已知条件,通过增加读指针m_readIndex和写指针m_writeIndex就可以解决循环读写问题。

char* buffer; // 环形缓冲区指针
uint32_t bufferSize; // 环形缓冲区大小
uint32_t readIndex; // 读指针,指向下一个要读取的位置
uint32_t writeIndex; // 写指针,指向下一个要写入的位置

计算公式

读索引 = 读指针 % 缓冲区长度; // 基础: 读写指针取模
写索引 = 写指针 % 缓冲区长度;读索引 = 读指针 & (缓冲区长度 - 1); // 优化: 当bufferSize为2的幂时,取模运算可优化为按位与
写索引 = 写指针 & (缓冲区长度 - 1);

假设 环形缓冲区大小bufferSize为4

  • 环形缓冲区的初始状态,大小为4

m_readIndex = 0
m_writeIndex = 0

 start              end  │                 │   ▼                 ▼   
┌─────┬─────┬─────┬─────┐
│     │     │     │     │
└─────┴─────┴─────┴─────┘▲  ▲                   │  │                   r  w                     
  • 环形缓冲区的写入1个数据A

m_readIndex = 0
m_writeIndex = 1 (1 % 4 = 1 或 1 & (4 -1) = 1)

 start              end  │                 │   ▼                 ▼   
┌─────┬─────┬─────┬─────┐
│  A  │     │     │     │
└─────┴─────┴─────┴─────┘▲     ▲               │     │               r     w              
  • 环形缓冲区的写入4个数据ABCD(缓冲区满)

m_readIndex = 0
m_writeIndex = 0 (指针回绕 4%4=0 或 4 & (4 -1) = 0)

 start              end  │                 │   ▼                 ▼   
┌─────┬─────┬─────┬─────┐
│  A  │  B  │  C  │  D  │
└─────┴─────┴─────┴─────┘▲ ▲                    │ │                    r w                             
  • 环形缓冲区的读取2个数据AB

m_readIndex = 2 (2 % 4 = 2 或 2& (4 -1) = 2)
m_writeIndex = 0

 start              end  │                 │   ▼                 ▼   
┌─────┬─────┬─────┬─────┐
│     │     │  C  │  D  │
└─────┴─────┴─────┴─────┘▲           ▲         │           │         w           r         

3.2 如何判断缓冲区空和满?

读写指针在相同位置时,缓冲区可能为空或者满。

可以用以下几种方法判断:

  • 计数法:维护写入个数count,count == 0 为空,count == bufferSize为满。需要通过原子操作保证线程安全
  • 预留空位法: 写入时保留最后一个位置。当(writeIndex + 1) % bufferSize == readIndex时为满,writeIndex == readIndex时为空
  • 镜像指示位法:通过镜像标志位标记读写指针(0至 2n-1)是否进入镜像区间(n 至 2n-1)。当读写指针相等且镜像标志位相同时为空,当读写指针相等且镜像标志位不同时为满。当缓冲区大小为2的幂时,可省略镜像标志位,使用异或运算writeIndex == (readIndex ^ bufferSize )直接判断满状态。RT-Thread、Linux内核(kfifo)采用该方案

3. 3 数据溢出与数据覆盖策略?

环形缓冲区的写入6个数据ABCDEF(缓冲区溢出,数据区满后写入数据模式为覆盖)

m_readIndex = 2 (覆盖了2个数据AB,需要移动2位)
m_writeIndex = 2( 6 % 4 = 2 或 6 & (4 -1) = 2)

 start              end  │                 │   ▼                 ▼   
┌─────┬─────┬─────┬─────┐
│  E  │  F  │  C  │  D  │
└─────┴─────┴─────┴─────┘▲ ▲        │ │        r w                

数据溢出时一般有以下几种策略:

  • 阻塞写入:抛出异常或阻塞等待缓冲区有足够空间,保证数据完整性(如日志记录)
  • 覆盖写入:覆盖旧数据,适用于实时流处理(如音视频传输)
  • 动态扩容:缓冲区大小扩容,但会破坏环形缓冲区的固定内存特性

Reference:

  1. Circular buffer - Wikipedia
  2. CSerialPort/include/CSerialPort/ibuffer.hpp

相关文章:

  • 自定义库模块增加自定义许可操作详细方法
  • 通义千问-langchain使用构建(三)
  • 毛泽东(井冈山)词三篇
  • Buildroot 移植MiniGUI: 编写简单示例(基于君正X2000)
  • SAP学习笔记 - 开发豆知识01 - CDS SDK命令出乱码 (cds init CAP-Test03 --add java)
  • 如何在 Windows 10 或 11 中安装 PowerShellGet 模块?
  • Day119 | 灵神 | 二叉树 | 二叉树的最近共公共祖先
  • SpringSecurity授权、认证
  • python如何做数据预处理
  • matlab求矩阵的逆、行列式、秩、转置
  • 编程基础:什么是变量
  • 【Linux笔记】nfs网络文件系统与autofs(nfsdata、autofs、autofs.conf、auto.master)
  • Linux面试题集合(6)
  • STM32外设DA实战-DAC + DMA 输出正弦波
  • 打卡Day28
  • 1.2 C++第一个程序
  • JavaScript splice() 方法
  • C语言斐波那契数列
  • 计算机视觉与深度学习 | Python实现EMD-CNN-LSTM时间序列预测(完整源码、数据、公式)
  • 【前端基础】11、CSS的属性特性(继承、层叠、元素类型、隐藏元素的四种方式)
  • 莫高义在第四届中国新闻发言人论坛开幕式上的致辞
  • 雅安市纪委监委回应黄杨钿甜耳环事件:相关政府部门正在处理
  • 消息人士称俄方反对美国代表参加俄乌直接会谈
  • 一涉嫌开设赌场的网上在逃人员在山东威海落网
  • 中国人民银行等四部门联合召开科技金融工作交流推进会
  • 特朗普促卡塔尔说服伊朗放弃核计划,伊朗总统:你来吓唬我们?