通用环形缓冲区 LwRB 使用指南

Posted 研究是为了理解

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了通用环形缓冲区 LwRB 使用指南相关的知识,希望对你有一定的参考价值。

什么是 LwRB?

LwRB 是一个开源、通用环形缓冲区库,为嵌入式系统进行了优化。源码点击这里(Github)。

LwRB 特性

  • 使用 ANSI C99 编写
  • FIFO (先进先出)
  • 无动态内存分配,数据是静态数组
  • 只有单个任务写并且单个任务读时,线程是安全的
  • 只有单个中断写并且单个中断读时,中断是安全的
  • 支持内存间的 DMA 操作,实现缓冲区和应用程序内存之间零拷贝
  • 对于读数据,提供 peek(窥读,读数据但不改变读指针) 、skip (跳读,向前移动读指针,将指定长度的数据标记为已读)函数,对于写数据,提供 advance (跳写,向前移动写指针,比如 DMA 硬件向环形缓冲区写入了 10 字节,应用程序需要调用此函数更新写指针)函数。
  • 支持事件通知

临界条件

定义 LwRB读指针r ,在读写操作时使用 r ,但仅在操作时修改 r
定义 LwRB写指针w ,在读写操作时使用 w ,但仅在操作时修改 w
定义 LwRB 的 环形缓冲区大小为 s所有操作都会使用,决不会修改 s

  • 环形缓冲区可以容纳的最大字节数总是为 s - 1 。这要求在初始化时,环形缓冲区的大小要比实际存储数据多一个字节
  • wr 指针总是指向下一个可写、可读位置;
  • w == r 时,环形缓冲区为
  • w == r - 1 时,环形缓冲区为

常规 API 函数

初始化函数

uint8_t lwrb_init(LWRB_VOLATILE lwrb_t* buff, void* buffdata, size_t size)

用法举例:

#define QUEUE_MAX_SIZE         (1024)
lwrb_t format_rb;
uint8_t format_data[sizeof(int) * QUEUE_MAX_SIZE + 1];

void init_format(void)

    lwrb_init(&format_rb, format_data, sizeof(format_data));

这里需注意环形缓冲区数组 format_data 的定义格式:

uint8_t format_data[sizeof(int) * QUEUE_MAX_SIZE + 1];

规定写入环形缓冲区或者从环形缓冲区读出的最小数据单位是 数据项数据项可能为 1 个字节,也可能为多个字节。
这里的例子数据项int 类型数据。
首先用宏 QUEUE_MAX_SIZE 定义需要保存的最大数据项数目,这里定义 1024数据项
环形缓冲区定义为 uint8_t 类型的数组。
环形缓冲区数组大小可以用以下公式确定:
环形缓冲区数组大小 = 数据项大小 ∗ 数据项个数 + 1 环形缓冲区数组大小 = 数据项大小 * 数据项个数 + 1 环形缓冲区数组大小=数据项大小数据项个数+1
这里 + 1 是因为 LwRB 的实现特性决定的,详见 临界条件 一节。

从环形缓冲区读数据

size_t lwrb_read(LWRB_VOLATILE lwrb_t* buff, void* data, size_t btr)

该函数最多读取 btr 字节的数据(如果有的话),数据从环形缓冲区拷贝到 data 指向的数组。函数返回实际读取的字节数。
用法举例:

lwrb_read(&format_rb, data_buf, sizeof(int));

向环形缓冲区写数据

size_t lwrb_write(LWRB_VOLATILE lwrb_t* buff, const void* data, size_t btw)

该函数最多写入 btw 字节的数据(如果可以的话),数据从 data 指向的数组拷贝到环形缓冲区。函数返回实际写入的字节数。
用法举例:

lwrb_write(&format_rb, data_buf, sizeof(int)); 

从环形缓冲区窥读数据

size_t lwrb_peek(LWRB_VOLATILE lwrb_t* buff, size_t skip_count, void* data, size_t btp)

函数 lwrb_peek 也可以从环形缓冲区读取最多 btp 字节的数据,数据被拷贝到 data 指向的数组。但是它和 lwrb_read 函数有两点不同:

  • 第一,lwrb_read 函数读取数据后会移动读指针,也就是读取数据后,环形缓冲区将这些数据移除掉;而 lwrb_peek 函数不移动读指针,这就意味着读取数据后,环形缓冲区不会将这些数据移除掉,就好像只是到环形缓冲区中看看这些数据都是什么样子,并不把数据拿出来,所以称为窥读
  • 第二,相比 lwrb_read 函数, lwrb_peek 函数多了一个 skip_count 参数。这个参数允许用户先跳过 skip_count 指定的字节数,再开始读取。

lwrb_peek 对一些场景十分有用。举一个我用到的例子:
设备与上位机通讯故障后,会将本地采集的不定长数据缓存起来,缓存的格式是:

然后将这些数据存储到环形缓冲区。等到设备与上位机恢复通讯,再将这些数据去除长度字段后上传给上位机。为了数据的可靠传输,设备必须等到上位机确认数据已经收到,才能将这些数据删除掉。
所以在程序设计中,先窥读长度字段,确认长度字段合法后,再窥读剩余数据。
因为使用窥读,所以数据仍保存在环形缓冲区中,直到上位机确认数据已经收到后,再将这些数据从环形缓冲区中删除(会用到尚未介绍的 API 函数)。代码如下:

#define DATA_LEN_NUM        	2			//长度字段占用的字节数
#define DATA_SEND_BUF_NUM		100			//数据字段最大字节数

read_count = lwrb_peek(&resume_rb_s, 0, resume_read_buf, DATA_LEN_NUM);	//窥读长度字段
/*环形缓冲区空处理*/
if(read_count != DATA_LEN_NUM)

	//处理
	return 0;

len = to_uint16_low_first(resume_read_buf);	//长度字段
/*长度字段合法性检查*/
if(len == 0 || len > DATA_SEND_BUF_NUM)
        
	ASSERT(0);
	//错误处理
	return 0;

lwrb_peek(&resume_nvrb_s, DATA_LEN_NUM, resume_read_buf, len);		//跳过长度字段窥读数据部分
//其它处理

从环形缓冲区跳读数据

size_t lwrb_skip(LWRB_VOLATILE lwrb_t* buff, size_t len)

该函数最多读取 len 字节的数据(如果有的话),数据并不会被保存到用户层,而是直接丢弃掉(环形缓冲区会删除这些数据),就像跳过了这些数据,所以称为跳读
跳读一般有两个用处:

  • 第一,和窥读(lwrb_peek)函数配合使用,就如窥读举例使用的场景:先窥读出数据,传送给上位机;上位机确认接收后,用跳读将这些数据丢弃掉。
  • 第二,使用硬件(比如 DMA )直接读取环形缓冲区数组后,需要用跳读将这些数据丢弃掉。这会在零拷贝一节中讲解。

以上是关于通用环形缓冲区 LwRB 使用指南的主要内容,如果未能解决你的问题,请参考以下文章

通用环形缓冲区 LwRB 使用指南

数据结构之环形缓冲器

使用无锁队列(环形缓冲区)注意事项

环形缓冲区

优秀的内存规划方法——环形缓冲区(ring buffer)

纯功能(持久)环形缓冲区