使用 Ring Buffer 完成数据传递

概述

在前述章节中,我们依次介绍了 Queue、StreamBuffer、MessageBuffer。上述三者尽管提供了一定的数据缓存能力,但没有提供管理数据溢出的能力。

数据溢出是指在数据缓存区容量一定的情况下,缓存区的空间已经被完全使用,但是仍旧有数据传入时的情况。

在前述的组件中,它们对数据溢出的处理是禁止新数据写入到缓存区,在数据不允许丢失的情况下,这通常没什么问题。但是一些情况下,这种处理方式通常是不可接收的,假设向屏幕上投放影片,在屏幕的缓存区已经用尽的情况下,禁止写入新数据,将导致整体的画面进入延时,此时,允许覆盖一部分“特别”旧的数据,同时保留一部分“较旧”的内容通常是可取的,这样可以实现画面比较流畅,而不至于停顿。

ring buffer 主要的特性其实可以用一句话概括:写满时,自动环绕。其区别于普通缓存区的特性是,一般缓存区写满时,就不允许写了,会返回写失败;ring buffer 在写满时,不会返回失败,会覆盖原先首部的数据,继续写。

ringbuf 基本的 API 包括创建、发送数据、接收数据、注销数据(只有注销即 return 后的数据其才允许被覆盖,即该缓冲区可以被再次写入新数据):

RingbufHandle_txRingbufferCreate(size_t xBufferSize, RingbufferType_txBufferType)BaseType_t xRingbufferSend(RingbufHandle_txRingbuffer, const void *pvItem, size_t xItemSize, TickType_t xTicksToWait)void *xRingbufferReceive(RingbufHandle_txRingbuffer, size_t *pxItemSize, TickType_t xTicksToWait)void vRingbufferReturnItem(RingbufHandle_txRingbuffer, void *pvItem)

值的注意的是,与 Queue、StreamBuffer、MessageBuffer 不同,ringbuf 在读取数据时,得到的数据的引用,即实际的存储在 ringbuf 的数据的指针,而不是拷贝的副本。

再次理解“ring"

很多初学者对 ringbuf 的理解总是想从 “ring”,即环绕的角度取理解,这往往使得对它的理解陷入一个奇怪的 ring。其是,没有一种真正的存储空间天生就是“环”(大部分存储器是线性的地址空间),所谓的“环”只是人为地添加的特性。可以这么说,ringbuf 就是一块缓冲区,在特性上通过读、写索引,实现读、写操作在到达缓冲区末尾时,可以通过配置指定,接下来的读写是否“重头开始”。

ring buf 的特点:

1)自动管理溢出时的处理。自动管理队头、队尾。

2)弹性的容量,即buffer 中的数据不会被销毁,直到下次被覆盖写入。

3)比链表要快,因为它内部使用数组(连续的内存),再次使用时,不必释放空间、移动其他数据也不必管理链表,只是重新赋值。

ring buf 的分类

在熟悉上面的概念后,可以理解 ESP-IDF 对 rIngbuf 的三种分类:

1)No-Split buffers:(禁止拆分型buffer)保证项目存储在连续内存中,并且在任何情况下(如环绕、内存空间不连续的情况)都不会尝试拆分项目。

2)Allow split buffers:即允许拆分缓冲区数据项存放在两个不连续的内存块中,如果这样做能够允许存储该条目。“允许拆分缓冲区”比“不拆分缓冲区”更节省内存,但在检索时需分两部分返回完整的原条目。

3)Byte buffer:不将数据存储为单独的项。所有数据都以字节序列的形式存储,并且每次发送或检索任意数量的字节。当不需要维护单独的项目(例如字节流)时,使用字节缓冲区。

写入 ring buf 数据时发生什么

如图显示为依次将大小为 18、3 和 27 字节的三个条目写入缓存区时,缓冲区内实际发生的情况:

对于“不拆分”和“允许拆分”缓冲区,每个数据项前面都有一个 8 个字节的标头, Byte buffer 没有标头。此外,每个项目占用的空间向上舍入到最接近的 32 位对齐大小,以保持整体 32 位对齐。但是,项目的真实大小记录在标头中,该标头将在检索项目时返回。

当数据满时,不可以拆分的 ringbuf 将强制回到缓冲区的头部开始存储新的条目。下图显示缓冲区大小为 128 字节,其中 56 个字节被标识为 free 可用空间,当要发送的项目为 28 字节时,由于是不可拆分的 ringbuf,即便尾部有 16 bytes 发空闲空间,也不会去存储 该 28bytes 的条目,如第二行所示,会在尾部填充占位符,然后如第三行所示,从缓冲区头部开始存储该长度为 28bytes 的条目:

需求及功能解析

ringbuffer 的内容颇多,这里仅介绍一种 no-split 类型的 ring buffer 使用的方法,并通过打印信息,反映ringbuffer 这种“自动环绕的”特性。

示例解析

示例中,ring buffer 的大小为 0xA0,下述 log,第 8 次发完数据后,读写位置都在 0x90 处,再往下写的话,重新从缓存区的头部开始写了,因此第9次的数据在 0x18 处,注意这里是十六进制,0x90 到 0x18 正好分为两段,0x900xA0,0x000x18,其中 0x90~0xA0 共计16 个字节的存储单元;0x00~0x18 共计 24 个字节的存储单元;因为前者不足以存储 8+16 的 large_item 这个 item,因此直接从 0x00 开始存储了,而不是从 0x90。

This is esp32 chip with 2 CPU core(s), WiFi/BT/BLE, Minimum free heap size: 295348 bytes
ring buffer Max item size is 40, num of items is 5
TASK2:flag=0
TASK2:free_pos=0x00000010, read_pos=0x00000010, write_pos=0x00000010, acquire_pos=0x00000010, num_of_items_pos=0x00000000
TASK2:flag=1
TASK2:free_pos=0x00000020, read_pos=0x00000020, write_pos=0x00000020, acquire_pos=0x00000020, num_of_items_pos=0x00000000
TASK2:flag=2
TASK2:free_pos=0x00000030, read_pos=0x00000030, write_pos=0x00000030, acquire_pos=0x00000030, num_of_items_pos=0x00000000
TASK2:flag=3
TASK2:free_pos=0x00000040, read_pos=0x00000040, write_pos=0x00000040, acquire_pos=0x00000040, num_of_items_pos=0x00000000
TASK2:flag=4
TASK2:free_pos=0x00000050, read_pos=0x00000050, write_pos=0x00000050, acquire_pos=0x00000050, num_of_items_pos=0x00000000
TASK2:flag=5
TASK2:free_pos=0x00000018, read_pos=0x00000018, write_pos=0x00000018, acquire_pos=0x00000018, num_of_items_pos=0x00000000
TASK2:flag=6
TASK2:free_pos=0x00000030, read_pos=0x00000030, write_pos=0x00000030, acquire_pos=0x00000030, num_of_items_pos=0x00000000
TASK2:flag=7
TASK2:free_pos=0x00000048, read_pos=0x00000048, write_pos=0x00000048, acquire_pos=0x00000048, num_of_items_pos=0x00000000

讨论

适合使用 ring buf 的几种场景

1)在预先知道缓存区需要存放数据的最大量时,可以用ringbuf,对需要频繁扩大缓冲区容量的可以使用链表。

2)允许覆盖旧数据的情况下,可以考虑使用 ringbuf。特别时多媒体处理时,比如,音频的生产者可以覆盖掉声卡暂为来得及处理的旧音频的数据。

3)数据并发处理。可以只读取数据,不 return 数据(删除数据),这样可以在多个线程中获取数据,然后分别执行对应的运算。

总结

1)Ring Buffer 提供了管理数据溢出的机制,当 Ring Buffer 的缓存区足够大时,数据写到缓冲区尾部后将自动从头部开始继续写。当 Ring Buffer 的缓冲区不够大时,即便存在数据溢出风险,但应用本身允许新数据覆盖旧数据的情况下使用 Ring Buffer 也非常合适。

2)ESP-IDF 对 rIngbuf 的三种分类:No-Split buffers、Allow split buffers、Byte buffer。

3)可以配置 rIngbuf 的缓冲区工作在连续内存中,由于 ringbuf 检索数据时是直接引用缓冲区的数据,连续的内存空间可以帮助提升访问效率。

资源链接

1)Learning-FreeRTOS-with-esp32 系列博客介绍
2)对应示例的 code 链接 (点击直达代码仓库)

3)下一篇:使用队列集进行传递数据或信号同步

使用 Ring Buffer 完成数据传递相关推荐

  1. Linux ftrace 1.1、ring buffer

    1.简介 ringbuffer是trace框架的一个基础,所有的trace原始数据都是通过ringbuffer记录的.ringbuffer的作用主要有几个: 1.存储在内存中,速度非常快,对系统性能的 ...

  2. 网卡中的Ring buffer -- 解决 rx_resource_errors 丢包

    1.软硬件环境 硬件: 飞腾E2000Q 平台 软件: linux 4.19.246 2.问题现象 网卡在高速收包的过程中,出现 rx error , 细查是 rx_resource_errors  ...

  3. ringbuffer java例子_使用Ring Buffer构建高性能的文件写入程序

    最近常收到SOD框架的朋友报告的SOD的SQL日志功能报错:文件句柄丢失.经过分析得知,这些朋友使用SOD框架开发了访问量比较大的系统,由于忘记关闭SQL日志功能所以出现了很高频率的日志写入操作,从而 ...

  4. SQL Server 环形缓冲区(Ring Buffer) -- 介绍

    SQL Server 环形缓冲区(Ring Buffer) -- 介绍 以下关于Ring Buffer的介绍转载自: http://zh.wikipedia.org/wiki/%E7%92%B0%E5 ...

  5. 什么是ring buffer?

    Ring Buffer 有什么特别? 原文地址: http://mechanitis.blogspot.com/2011/06/dissecting-disruptor-whats-so-specia ...

  6. Android Binder 分析——数据传递者(Parcel)

    前面 binder 原理和通信模型中在接口实现部分(Bp 和 Bn)中应该看到很多地方都有使用 parcel.这个 android 专门设计用来跨进程传递数据的,实现在 native,java 层有接 ...

  7. Java 环形缓冲器(Ring Buffer)

    环形缓冲器(Ring Buffer):环形队列,这里使用数组实现,但并未用上环形功能,因为设置了队满直接出队清空队列,如果只读取部分数据,又或者想要覆盖冲写,则可以用上环形功能 package cha ...

  8. SQL Server 环形缓冲区(Ring Buffer) -- 环形缓冲在AlwaysOn的应用

    SQL Server 环形缓冲区(Ring Buffer) -- 环形缓冲在AlwaysOn的应用 可以从SQL Server环形缓冲区得到一些诊断AlwaysOn的信息,或从sys.dm_os_ri ...

  9. 解析Disruptor:写入ring buffer

    原文地址http://mechanitis.blogspot.com/2011/07/dissecting-disruptor-writing-to-ring.html 这是Disruptor end ...

最新文章

  1. [新手必看] 17个常见的Python运行时错误
  2. springboot拦截器@Autowired为null解决
  3. 深度学习 end2end
  4. java面向对象(类与对象,局部变量成员变量,基本类型和引用类型作为参数传递)...
  5. win10商店下载位置_Win10删应用商店下载记录|Win10删Microsoft Store下载记录
  6. react脚手架 显示npm不知内部命令_第一章 React开发环境搭建
  7. Docker代理设置方法
  8. 毕业三年,同学基本都辞职了,大部分人看完很有同感~
  9. mysql 使用存储过程批量插数据
  10. 深度解码企业文化体系
  11. 用cmd 查看本机的IP地址
  12. gg修改器修改内购_【教程】手把手教你修改微信性别为空
  13. C语言小熊时钟实验报告,用VC6.0编写一个小熊时钟(求代码)
  14. 迷失在JAVA的咖啡杯中
  15. Oracle select表要带双引号的原因
  16. Visitor(访问者)示例
  17. PureMVC 实例讲解
  18. Nexus(1):Nexus的安装与配置
  19. 第六章:纯策略纳什均衡【小结】
  20. 工业控制系统的安全建议

热门文章

  1. 时间序列分析-ARIMA模型
  2. 「津津乐道播客」#190. 听播客是一种双向行为么?
  3. 华为P7如何官方解锁
  4. [指导]Lenovo ThinkPad E550 (Haswell)使用clover引导驱动macOS EIcaption
  5. 16.策略模式能解决什么问题?
  6. 【毕业设计专栏】基于SpringBoot+Vue学生综合测评系统【源码+论文+演示PPT视频】
  7. 谈什么品牌的显示器最好
  8. R语言横向合并数据库 merge
  9. Unicode(统一码、万国码、单一码)
  10. 窥探Windows UUP 正向差分更新机制的高效性