环形缓冲器

2023-10-30

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

中文名 外文名 别名 类型
环形缓冲器 Ring Buffer 循环缓冲区、圆形缓冲区 集合、容器

简介

      在通信程序中,经常使用环形缓冲器作为数据结构来存放通信中发送和接收的数据。环形缓冲区是一个先进先出的循环缓冲区,可以向通信程序提供对缓冲区的互斥访问。

用法

      圆形缓冲区的一个有用特性是:当一个数据元素被用掉后,其余数据元素不需要移动其存储位置。相反,一个非圆形缓冲区(例如一个普通的队列)在用掉一个数据元素后,其余数据元素需要向前搬移。换句话说,圆形缓冲区适合实现先进先出缓冲区,而非圆形缓冲区适合后进先出缓冲区。

      圆形缓冲区适合于事先明确了缓冲区的最大容量的情形。扩展一个圆形缓冲区的容量,需要搬移其中的数据。因此一个缓冲区如果需要经常调整其容量,用链表实现更为合适。

      写操作覆盖圆形缓冲区中未被处理的数据在某些情况下是允许的。特别是在多媒体处理时。例如,音频的生产者可以覆盖掉声卡尚未来得及处理的音频数据。

工作过程

      一个圆形缓冲区最初为空并有预定的长度。例如,这是一个具有七个元素空间的圆形缓冲区,其中底部的单线与箭头表示“头尾相接”形成一个圆形地址空间:

      假定1被写入缓冲区中部(对于圆形缓冲区来说,最初的写入位置在哪里是无关紧要的):

      再写入2个元素,分别是2 & 3 — 被追加在1之后:

      如果两个元素被处理,那么是缓冲区中最老的两个元素被移除。在本例中,1 & 2被移除,缓冲区中只剩下3:

      如果缓冲区中有7个元素,则是满的:

      如果缓冲区是满的,又要写入新的数据,一种策略是覆盖掉最老的数据。此例中,2个新数据— A & B — 写入,覆盖了3 & 4:

      也可以采取其他策略,禁止覆盖缓冲区的数据,采取返回一个错误码或者抛出异常。

      最终,如果从缓冲区中移除2个数据,不是3 & 4 而是 5 & 6 。因为 A & B 已经覆盖了3 & 4:

圆形缓冲区工作机制

    由于计算机内存是线性地址空间 [1]  ,因此圆形缓冲区需要特别的设计才可以从逻辑上实现。

读指针与写指针

    一般的,圆形缓冲区需要4个指针 [2]  :

  • 在内存中实际开始位置;

  • 在内存中实际结束位置,也可以用缓冲区长度代替;

  • 存储在缓冲区中的有效数据的开始位置(读指针);

  • 存储在缓冲区中的有效数据的结尾位置(写指针)。

    读指针、写指针可以用整型值来表示。下例为一个未满的缓冲区的读写指针:

    下例为一个满的缓冲区的读写指针:

区分缓冲区满或者空

      缓冲区是满、或是空,都有可能出现读指针与写指针指向同一位置:

      有多种策略用于检测缓冲区是满、或是空.

总是保持一个存储单元为空

      缓冲区中总是有一个存储单元保持未使用状态。缓冲区最多存入  个数据。如果读写指针指向同一位置,则缓冲区为空。如果写指针位于读指针的相邻后一个位置,则缓冲区为满。这种策略的优点是简单、鲁棒;缺点是语义上实际可存数据量与缓冲区容量不一致,测试缓冲区是否满需要做取余数计算。

使用数据计数

      这种策略不使用显式的写指针,而是保持着缓冲区内存储的数据的计数。因此测试缓冲区是空是满非常简单;对性能影响可以忽略。缺点是读写操作都需要修改这个存储数据计数,对于多线程访问缓冲区需要并发控制。

镜像指示位

      缓冲区的长度如果是 n,逻辑地址空间则为  至  ;那么,规定  至  为镜像逻辑地址空间。本策略规定读写指针的地址空间为  至  ,其中低半部分对应于常规的逻辑地址空间,高半部分对应于镜像逻辑地址空间。当指针值大于等于 时,使其折返(wrapped)到  。使用一位表示写指针或读指针是否进入了虚拟的镜像存储区:置位表示进入,不置位表示没进入还在基本存储区。

      在读写指针的值相同情况下,如果二者的指示位相同,说明缓冲区为空;如果二者的指示位不同,说明缓冲区为满。这种方法优点是测试缓冲区满/空很简单;不需要做取余数操作;读写线程可以分别设计专用算法策略,能实现精致的并发控制。 缺点是读写指针各需要额外的一位作为指示位。

      如果缓冲区长度是2的幂,则本方法可以省略镜像指示位。如果读写指针的值相等,则缓冲区为空;如果读写指针相差n,则缓冲区为满,这可以用条件表达式(写指针 == (读指针 异或 缓冲区长度))来判断。

// This approach adds one bit to end and start pointers
// Circular buffer object
typedef struct {
    int size; // maximum number of elements
    int start; // index of oldest element
    int end; // index at which to write new element
    ElemType *elems; // vector of elements
} CircularBuffer;
 
void cbInit(CircularBuffer *cb, int size) {
    cb->size = size;
    cb->start = 0;
    cb->end = 0;
    cb->elems = (ElemType *)calloc(cb->size, sizeof(ElemType));
}
 
void cbPrint(CircularBuffer *cb) {
    printf("size = 0x%x, start = %d, end = %d\n", cb->size, cb->start, cb->end);
}
 
int cbIsFull(CircularBuffer *cb) {
// This inverts the most significant bit of start before comparison
    return cb->end == (cb->start ^ cb->size);
int cbIsEmpty(CircularBuffer *cb) {
    return cb->end == cb->start;
}
 
int cbIncr(CircularBuffer *cb, int p) {
// start and end pointers incrementation is done modulo 2*size
 
    return (p + 1) & (2 * cb->size - 1); 
 
void cbWrite(CircularBuffer *cb, ElemType *elem) {
    cb->elems[cb->end & (cb->size - 1)] = *elem;
    if (cbIsFull(cb)) // full, overwrite moves start pointer
        cb->start = cbIncr(cb, cb->start);
    cb->end = cbIncr(cb, cb->end);
}
 
void cbRead(CircularBuffer *cb, ElemType *elem) {
    *elem = cb->elems[cb->start & (cb->size - 1)];
    cb->start = cbIncr(cb, cb->start);
}

读写计数
       用两个有符号整型变量分别保存写入、读出缓冲区的数据数量。其差值就是缓冲区中尚未被处理的有效数据的数量。这种方法的优点是读线程、写线程互不干扰;缺点是需要额外两个变量。
记录最后的操作
      使用一位记录最后一次操作是读还是写。读写指针值相等情况下,如果最后一次操作为写入,那么缓冲区是满的;如果最后一次操作为读出,那么缓冲区是空。 这种策略的缺点是读写操作共享一个标志位,多线程时需要并发控制。

POSIX优化实现

#include <sys/mman.h>
#include <stdlib.h>
#include <unistd.h>
 
#define report_exceptional_condition() abort ()
 
struct ring_buffer {
    void *address;
    unsigned long count_bytes;
    unsigned long write_offset_bytes;
    unsigned long read_offset_bytes;
};
 
// Warning order should be at least 12 for Linux
void ring_buffer_create (struct ring_buffer *buffer, unsigned long order) {
    char path[] = "/dev/shm/ring-buffer-XXXXXX";
    int file_descriptor;
    void *address;
    int status;
    file_descriptor = mkstemp(path);
    if (file_descriptor < 0)
        report_exceptional_condition();
    status = unlink(path);
    if (status)
        report_exceptional_condition();
    buffer->count_bytes = 1UL << order;
    buffer->write_offset_bytes = 0;
    buffer->read_offset_bytes = 0;
    status = ftruncate(file_descriptor, buffer->count_bytes);
    if (status)
        report_exceptional_condition();
    buffer->address = mmap (NULL, buffer->count_bytes << 1, PROT_NONE,
                            MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
    if (buffer->address == MAP_FAILED)
        report_exceptional_condition();
    address =
        mmap(buffer->address, buffer->count_bytes, PROT_READ | PROT_WRITE,
             MAP_FIXED | MAP_SHARED, file_descriptor, 0);
    if (address != buffer->address)
        report_exceptional_condition();
    address = mmap(buffer->address + buffer->count_bytes,
                   buffer->count_bytes, PROT_READ | PROT_WRITE,
                   MAP_FIXED | MAP_SHARED, file_descriptor, 0);
    if (address != buffer->address + buffer->count_bytes)
        report_exceptional_condition();
    status = close(file_descriptor);
    if (status)
        report_exceptional_condition();
}
 
void ring_buffer_free(struct ring_buffer *buffer) {
    int status;
    status = munmap(buffer->address, buffer->count_bytes << 1);
    if (status)
        report_exceptional_condition ();
}
 
void *ring_buffer_write_address(struct ring_buffer *buffer) {
    // void pointer arithmetic is a constraint violation.
    return buffer->address + buffer->write_offset_bytes;
}
 
void ring_buffer_write_advance(struct ring_buffer *buffer, unsigned long count_bytes) {
    buffer->write_offset_bytes += count_bytes;
}
 
void *ring_buffer_read_address(struct ring_buffer *buffer) {
    return buffer->address + buffer->read_offset_bytes;
}
 
void ring_buffer_read_advance(struct ring_buffer *buffer, unsigned long count_bytes) {
    buffer->read_offset_bytes += count_bytes;
    if (buffer->read_offset_bytes >= buffer->count_bytes) {
        //如果读指针大于等于缓冲区长度,那些读写指针同时折返回[0, buffer_size]范围内
        buffer->read_offset_bytes -= buffer->count_bytes;
        buffer->write_offset_bytes -= buffer->count_bytes;
    }
}
 
unsigned long ring_buffer_count_bytes(struct ring_buffer *buffer) {
    return buffer->write_offset_bytes - buffer->read_offset_bytes;
}
 
unsigned long ring_buffer_count_free_bytes(struct ring_buffer *buffer) {
    return buffer->count_bytes - ring_buffer_count_bytes (buffer);
}
 
void ring_buffer_clear(struct ring_buffer *buffer) {
    buffer->write_offset_bytes = 0;
    buffer->read_offset_bytes = 0;
}
/*  Note, that initial anonymous mmap() can be avoided - after initial mmap() 
        for descriptor fd, you can try mmap() with hinted address as (buffer->address 
        + buffer->count_bytes) and if it fails - another one with hinted address as 
        (buffer->address - buffer->count_bytes). Make sure MAP_FIXED is not used in 
        such case, as under certain situations it could end with segfault. The 
        advantage of such approach is, that it avoids requirement to map twice the 
        amount you need initially (especially useful e.g. if you want to use 
        hugetlbfs and the allowed amount is limited) and in context of gcc/glibc 
        - you can avoid certain feature macros (MAP_ANONYMOUS usually requires one 
        of: _BSD_SOURCE, _SVID_SOURCE or _GNU_SOURCE). */

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

环形缓冲器 的相关文章

随机推荐

  • 刷脸支付抓住机会将财富收入囊中

    目前刷脸支付很多地方都已经开始落地商业 2019年相比支付行业最火爆的项目应该就是刷脸支付代理了 相信很多消费者也都体验到了刷脸支付带给我们的便利性和智能化的体验 而对于商家是大大节省人力和时间成本 加盟刷脸支付项目有很大的商机和发展前景
  • 信号完整性分析:关于传输线的三十个问题解答(二)

    11 对于 50 欧姆带状线的纵横比 什么是好的经验法则 What is a good rule of thumb for the aspect ratio of a 50 Ohm stripline 在带状线几何形状和 FR4 基板中 线
  • 信息度量——熵

    1 熵 1 1 熵的定义和理解 热力学用熵值描述系统混乱程度或不确定程度 香农用信息熵的概念来描述信源的不确定度 信息量与信息熵是相对的 告诉你一件事实 你获取了信息量 但减少了熵 或者说 得知一件事实后信息熵减少的量 就是你得到的这个事实
  • 例题讲解拉格朗日乘子法、线性可分支持向量机(SVM)的推导

    支持向量机 Support Vector Machine SVM 于1995年被首次提出 在解决小样本 非线性及高维度模式识别模式中具有许多特有的优势 1 SVM的相关概念 在介绍SVM之前需要了解一些相关概念 最优分类超平面 分类超平面方
  • flutter 使用image_picker上传图片

    第一步 封装 可以单独放在一个文件里 可以直接复制 选择图片函数 拍照 HspTakePhoto async var image await ImagePicker pickImage source ImageSource camera m
  • React 全栈体系(六)

    第三章 React 应用 基于 React 脚手架 二 组件的组合使用 TodoList 3 添加 todo 3 1 App src App jsx 创建 外壳 组件App import React Component from react
  • 后端返回JSON数据格式,前端根据JSON数据 导出.CSV文件

    以下仅供参考 效果图 前端JSON导出CSV文件 param Object dataObj 对象 title 名称 jsonKey Name 键值对 key data JSON数据 fileName 文件名 function exportC
  • Java中的OIO和NIO详解(含代码)

    简介及示例 Java NIO New I O 和OIO Old I O 是Java提供的两种不同的I O模型 OIO Old I O 是传统的阻塞I O模型 也称为同步I O 在OIO模型中 每个I O操作 如读写操作 都会阻塞当前线程 直
  • 随手记录(日历)

    日历
  • 7.最大最小距离算法与最大最小距离

    7 最大最小距离算法与最大最小距离 最大最小距离算法 最大最小距离算法是一种聚类算法 算法描述 1 任意选取一个样本模式作为第一聚类中心K1 2 选择离Z1最远欧氏距离的模式样本作为第二聚类中心K2 3 逐个计算每个模式样本与已确定的所有聚
  • 哈希表(散列表)原理详解

    什么是哈希表 哈希表 Hash table 也叫散列表 是根据关键码值 Key value 而直接进行访问的数据结构 也就是说 它通过把关键码值映射到表中一个位置来访问记录 以加快查找的速度 这个映射函数叫做散列函数 存放记录的数组叫做散列
  • Kibana启动Kibana server is not ready yet

    问题 页面访问Kibana路径显示 Kibana server is not ready yet 原因1 启动Kibana时指定ElasticSearch地址错误 http 116 62 19 81 9200 需要改为自己本机服务器的ip和
  • python调用GPT实现:智能用例生成工具

    工具作用 根据输入的功能点 生成通用测试点 实现步骤 工具实现主要分2个步骤 1 https请求调用Gpt 将返回响应结果保存为 md文件 2 用python实现 将 md文件转换成 xmind文件 3 写个简单的前端页面 调用上述步骤接口
  • zabbix-server仪表板出现: 不

    1 检查配置文件 vi etc zabbix zabbix server conf 里面的配置项是否还是原始的 如果是 请修改如下 2 检查第二个配置文件 vi etc zabbix web zabbix conf php 修改之前的原始配
  • 未转变者怎么调服务器难度,Unturned——作弊模式下的各项数值微调【较实用的已详细描述】...

    您尚未登录 立即登录享受更好的浏览体验 您需要 登录 才可以下载或查看 没有帐号 注册 register x 本帖最后由 Crazy Zombie 于 2017 8 11 10 31 编辑 如标题所示 在下发一个关于Unturned模式下各
  • 区块链与哈希函数

    目录 哈希函数 定义 性质 发展 常见攻击方法 1 穷举攻击 2 生日攻击 3 其他攻击 构造方法 1 利用对称密码体制来设计哈希函数 2 直接设计哈希函数 编辑 常用哈希函数简介 1 SHA 256算法 编辑 2 Keccak算法 3 S
  • Rust 学习心得<3>:无栈协程

    Rust 学习心得 lt 3 gt 无栈协程 有栈协程 vs 无栈协程 Go 有栈协程 Rust 协程 绿色线程 GreenThread 无栈协程 协程解决的问题 Rust作为一门新兴语言 主打系统编程 提供了多种编写代码的模式 Rust在
  • 虚幻引擎入门_框架

    虚幻引擎所提供的GamePlay框架可谓是虚幻引擎最为重要的一部分内容也不为过 虚幻引擎的设计希望开发人员在使用引擎之前是准备好了的 并且有充足的能力去理解游戏设计意图 在此之上为我们提供了一套开发规则 我们称之为游戏框架 GamePlay
  • 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

    1 给定一个字符串 验证它是否是回文串 只考虑字母和数字字符 可以忽略字母的大小写 说明 本题中 我们将空字符串定义为有效的回文串 示例 1 输入 A man a plan a canal Panama 输出 true 示例 2 输入 ra
  • 环形缓冲器

    环形缓冲器 ringr buffer 也称作圆形队列 circular queue 循环缓冲区 cyclic buffer 圆形缓冲区 circula buffer 是一种用于表示一个固定尺寸 头尾相连的缓冲区的数据结构 适合缓存数据流 中