Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
ringbuffer_环形缓冲区
RT-Thread 隐藏的宝藏之ringbuff
发布于 2021-03-21 11:40:13 浏览:2163
订阅该版
[tocm] ## 一,什么是 ringbuff **ringbuff**: 翻译成中文就是环形缓冲区。 网上关于 `ringbuff` 的介绍已经非常多了,我也分享一下我对 `ringbuff` 的认识。`ringbuff` 可以理解为学校操场上的跑道,`ringbuff` 初始化就是新建了一个 `400` 的环形操场跑道,跑道上有 2 名同学 (`write` 和 `read`),每次写入一个数据,`write` 往前跑一步,每次读一个数据,`read` 往前跑一步,`read`永远不可以超过 `write`。刚开始起点都是 `0` ,如果 `write` 写的速度慢了,被 `read` 追上了,那么 `ringbuff` 缓存数据就为空。 如果 `write` 写的速度快了,`read` 读慢了,被 `write` 跑了一圈又追上了,那么 `ringbuff` 缓存区就是满。 这里借用一下他人的图,[原图地址](https://blog.csdn.net/lu_embedded/article/details/107308740) , 侵删。 ![screenshot_20200713003626257.png](https://oss-club.rt-thread.org/uploads/20241012/7f89826a425958b13683a3cb4320074f.png.webp) ## 二. 怎么使用 ringbuff 在知道怎么使用 `ringbuff` 前先要看一下,`ringbuff` 的结构体: ```c 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; }; ``` - *buffer_ptr : 缓冲区指针 - read_mirror :读取镜像。可以理解为一张白纸,读完了就翻过一页再读 - read_index :读数据的位置 - write_mirror : 写入镜像。可以理解为一张白纸,写完了就翻过一页再写 - write_index : 写入数据的位置 - buffer_size : 缓存区大小 这里有一个 **C 语言** 的小知识点:**位域** 。 这里使用了一个 `u16` 存储了 `read_mirror` 和 `read_index` , 所以 `index` 最大只能到 32767 。这个大小满足绝大数的场景。 ##### 1 . 初始化 ringbuff ``` void rt_ringbuffer_init(struct rt_ringbuffer *rb, rt_uint8_t *pool, rt_int16_t size) ``` - *rb : 环形缓冲区句柄 - *pool : 缓存区指针 - size : 缓存区大小 ##### 2 . 往 ringbuff 写入数据 ```c rt_size_t rt_ringbuffer_put(struct rt_ringbuffer *rb, const rt_uint8_t *ptr, rt_uint16_t length) ``` *rb : 环形缓冲区句柄 *ptr : 写入数据的指针 length : 写入数据的长度 ##### 3 . 强制往 ringbuff 写入数据 ``` rt_size_t rt_ringbuffer_put_force(struct rt_ringbuffer *rb, const rt_uint8_t *ptr, rt_uint16_t length) ``` *rb : 环形缓冲区句柄 *ptr : 写入数据的指针 length : 写入数据的长度 强制写数据使用场景:`ringbuff` 缓存区中的数据已经满了,但是还要写入数据。这种情况将会导致前面写入的数据被覆盖,导致数据丢失,请谨慎使用。 ##### 4. 从 ringbuff 读取数据 ``` rt_size_t rt_ringbuffer_get(struct rt_ringbuffer *rb, rt_uint8_t *ptr, rt_uint16_t length) ``` *rb : 环形缓冲区句柄 *ptr : 读数据的存放的位置 length : 读取的长度 ##### 5. 往 ringbuff 写入一个字节的数据 ``` rt_size_t rt_ringbuffer_putchar(struct rt_ringbuffer *rb, const rt_uint8_t ch) ``` *rb : 环形缓冲区句柄 ch : 将要写入的数据 ##### 6. 强制往 ringbuff 写入一个字节的数据 ``` rt_size_t rt_ringbuffer_putchar_force(struct rt_ringbuffer *rb, const rt_uint8_t ch) ``` *rb : 环形缓冲区句柄 ch : 将要写入的数据 注意强制写入将会导致数据被覆盖。 ##### 7. 从 ringbuff 读取一个字节的数据 ``` rt_size_t rt_ringbuffer_getchar(struct rt_ringbuffer *rb, rt_uint8_t *ch) ``` *rb : 环形缓冲区句柄 *ch : 存储读取的一个字节的变量 ##### 8. 获取 ringbuff 中已经使用的控件 ``` rt_size_t rt_ringbuffer_data_len(struct rt_ringbuffer *rb) ``` *rb : 环形缓冲区句柄 ##### 9. 复位 ringbuff ``` void rt_ringbuffer_reset(struct rt_ringbuffer *rb) ``` *rb : 环形缓冲区句柄 ##### 10. 创建 ringbuff ``` struct rt_ringbuffer* rt_ringbuffer_create(rt_uint16_t size) ``` size : 唤醒缓冲区的大小 `rt_ringbuffer_create` 在函数内部实现了 `pool` 然后调用了 `rt_ringbuffer_init` ##### 11. 销毁 ringbuff ``` void rt_ringbuffer_destroy(struct rt_ringbuffer *rb) ``` 这个只能销毁调用 `rt_ringbuffer_create` 创建的 `ring_buff` ##### 12 . 获取 ringbuff 的大小 ``` rt_inline rt_uint16_t rt_ringbuffer_get_size(struct rt_ringbuffer *rb) ``` ## 三. 原理分析 ringbuff ##### 1. 初始化 ringbuff ``` void rt_ringbuffer_init(struct rt_ringbuffer *rb, rt_uint8_t *pool, rt_int16_t size) { /* initialize read and write index */ rb->read_mirror = rb->read_index = 0;// 初始化时 读下标设置为 0 rb->write_mirror = rb->write_index = 0;// 初始化时 写下标设置为 0 /* set buffer pool and size */ rb->buffer_ptr = pool;// 设置 ring_buff 的缓存区地址 rb->buffer_size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE);// 保证 size 的大小是对齐的 } ``` ##### 2. 往 ringbuff 写入数据 ``` rt_size_t rt_ringbuffer_put(struct rt_ringbuffer *rb, const rt_uint8_t *ptr, rt_uint16_t length) { rt_uint16_t size; /* whether has enough space */ size = rt_ringbuffer_space_len(rb);// 获取 ring_buff 中可用空间的大小 /* no space */ if (size == 0) return 0; // 如果空间不够 直接返回 /* drop some data */ if (size < length) // 如果缓存区的控件不够保存这一次数据, 则把能够写入的这一部分数据写进去 length = size; if (rb->buffer_size - rb->write_index > length) {// 这里判断的是数据能够在第一个镜像写完,就直接写入当前的镜像,可以理解为在白纸上写数据,这个时候不需要翻页 /* read_index - write_index = empty space */ memcpy(&rb->buffer_ptr[rb->write_index], ptr, length); /* this should not cause overflow because there is enough space for * length of data in current mirror */ rb->write_index += length; return length; // 返回写入数据的长度 } memcpy(&rb->buffer_ptr[rb->write_index],// 把能够写入第一个镜像的数据线写入第一个镜像的缓存区 &ptr[0], rb->buffer_size - rb->write_index); memcpy(&rb->buffer_ptr[0],// 前一个镜像写满了,就要从头写了,这里就体现了循环 &ptr[rb->buffer_size - rb->write_index], length - (rb->buffer_size - rb->write_index)); /* we are going into the other side of the mirror */ rb->write_mirror = ~rb->write_mirror;// 完成 镜像 0 和 1 的翻转 rb->write_index = length - (rb->buffer_size - rb->write_index);// 重新设置写数据的小标 return length;// 返回写入数据的长度 } ``` ##### 3. 强制往 ringbuff 写入数据 ``` rt_size_t rt_ringbuffer_put_force(struct rt_ringbuffer *rb, const rt_uint8_t *ptr, rt_uint16_t length) { rt_uint16_t space_length; space_length = rt_ringbuffer_space_len(rb); // 获取可用缓存区大小 if (length > rb->buffer_size) // 如果长度超过了当前 ring_buff 的最大值 { ptr = &ptr[length - rb->buffer_size];// 获取超出部分的地址 length = rb->buffer_size;// lenght 设置为 ring_buff 的大小 } if (rb->buffer_size - rb->write_index > length)// 如果足够存储这次将要写入的数据 { /* read_index - write_index = empty space */ memcpy(&rb->buffer_ptr[rb->write_index], ptr, length);// 拷贝数据 /* this should not cause overflow because there is enough space for * length of data in current mirror */ rb->write_index += length;// 移动 write index if (length > space_length) // 如果长度大于可用缓存区大小 rb->read_index = rb->write_index;// 因为数据已经被覆盖掉了,所以 read 的 index 必须往前移动 return length; // 返回写入数据的长度 } memcpy(&rb->buffer_ptr[rb->write_index],// 拷贝数据在当前镜像 &ptr[0], rb->buffer_size - rb->write_index); memcpy(&rb->buffer_ptr[0],// 拷贝数据到下一个镜像 &ptr[rb->buffer_size - rb->write_index], length - (rb->buffer_size - rb->write_index)); /* we are going into the other side of the mirror */ rb->write_mirror = ~rb->write_mirror;// 镜像翻转 rb->write_index = length - (rb->buffer_size - rb->write_index); // 写完数据后移动 write index if (length > space_length) // 如果长度大于可用空间 { rb->read_mirror = ~rb->read_mirror; // 翻转读镜像 rb->read_index = rb->write_index; // 移动 read_index 到 write_index } return length; } ``` ##### 4. 从 ringbuff 获取数据 ``` rt_size_t rt_ringbuffer_get(struct rt_ringbuffer *rb, rt_uint8_t *ptr, rt_uint16_t length) { rt_size_t size; /* whether has enough data */ size = rt_ringbuffer_data_len(rb);// 检查当前 ringbuff 中是否有足够的空间 /* no data */ if (size == 0) return 0; // 如果没有数据则直接返回 /* less data */ if (size < length) length = size;// 如果已经存在的数据小于想要获取的长度,那么就会把长度设置为 size if (rb->buffer_size - rb->read_index > length)// 如果在一个镜像中就能获取到所有的数据 { /* copy all of data */ memcpy(ptr, &rb->buffer_ptr[rb->read_index], length);// 拷贝数据 /* this should not cause overflow because there is enough space for * length of data in current mirror */ rb->read_index += length; // 移动读下标 return length;// 返回读取到的长度 } /* 能执行到这里说明,一个镜像内无法读取到所有的数据 */ memcpy(&ptr[0], &rb->buffer_ptr[rb->read_index], rb->buffer_size - rb->read_index);// 拷贝当前镜像的数据 memcpy(&ptr[rb->buffer_size - rb->read_index], &rb->buffer_ptr[0], length - (rb->buffer_size - rb->read_index));// 拷贝剩余的数据 /* we are going into the other side of the mirror */ rb->read_mirror = ~rb->read_mirror;// 翻转镜像 rb->read_index = length - (rb->buffer_size - rb->read_index);// 移动读下标 return length; } ``` ##### 5. 往 ringbuff 中写入一个字符 ``` rt_size_t rt_ringbuffer_putchar(struct rt_ringbuffer *rb, const rt_uint8_t ch) { /* whether has enough space */ if (!rt_ringbuffer_space_len(rb)) // 没有足够的空间就直接返回了 return 0; rb->buffer_ptr[rb->write_index] = ch;// 把这个字符写入到缓冲区的指定位置 /* flip mirror */ if (rb->write_index == rb->buffer_size-1)// 检查写入这个字符后,当前镜像是否写满 { rb->write_mirror = ~rb->write_mirror;// 翻转镜像 rb->write_index = 0;// 设置下标为0 } else { rb->write_index++; // 下标加1 } return 1; // 写入一个字符,返回 1 } ``` ##### 6. 往 ringbuff 强制写入一个字符 ``` rt_size_t rt_ringbuffer_putchar_force(struct rt_ringbuffer *rb, const rt_uint8_t ch) { enum rt_ringbuffer_state old_state; old_state = rt_ringbuffer_status(rb);// 获取状态 rb->buffer_ptr[rb->write_index] = ch;// 写入数据 /* flip mirror */ if (rb->write_index == rb->buffer_size-1) // 检查当前镜像是不是满了 { rb->write_mirror = ~rb->write_mirror; // 翻转写镜像 rb->write_index = 0;// 翻转之后设置下标为 0 if (old_state == RT_RINGBUFFER_FULL) // 如果 ringbuff 的状态是满 { rb->read_mirror = ~rb->read_mirror; // 翻转读镜像 rb->read_index = rb->write_index; // 设置读下标和写下标一致 } } else { rb->write_index++; // 写下标加1 if (old_state == RT_RINGBUFFER_FULL) rb->read_index = rb->write_index;// 如果满,设置读下标等于写下标 } return 1; // 写入一个字符,返回1 } ``` ##### 7. 从 ringbuff 获取一个字符 ``` rt_size_t rt_ringbuffer_getchar(struct rt_ringbuffer *rb, rt_uint8_t *ch) { /* ringbuffer is empty */ if (!rt_ringbuffer_data_len(rb)) // 检查 ringbuff 是否为空 return 0; /* put character */ *ch = rb->buffer_ptr[rb->read_index];// 获取当前读下标的数据 if (rb->read_index == rb->buffer_size-1)// 如果当前镜像满了 { rb->read_mirror = ~rb->read_mirror;// 翻转镜像 rb->read_index = 0; // 设置读数据的下标为0 } else { rb->read_index++; // 下标加1 } return 1;// 读取一个字节,返回1 } ``` ##### 8. 获取 ringbuff 中数据的长度 ``` rt_size_t rt_ringbuffer_data_len(struct rt_ringbuffer *rb) { switch (rt_ringbuffer_status(rb)) // 获取 ringbuff 的状态 { case RT_RINGBUFFER_EMPTY: return 0; // 空就返回 0 case RT_RINGBUFFER_FULL: return rb->buffer_size; // 满就返回缓冲区的大小 case RT_RINGBUFFER_HALFFULL:// 半满 default: if (rb->write_index > rb->read_index) // 如果在同一镜像 return rb->write_index - rb->read_index;// 返回下标差 else return rb->buffer_size - (rb->read_index - rb->write_index);// 如果不在同一镜像,通过计算获取数据的长度 }; } ``` ##### 9. 重置 ringbuff ``` void rt_ringbuffer_reset(struct rt_ringbuffer *rb) { rb->read_mirror = 0; rb->read_index = 0; rb->write_mirror = 0; rb->write_index = 0; } // 所有的值都设置为 0 ``` ##### 10. 创建一个 ringbuff ``` struct rt_ringbuffer* rt_ringbuffer_create(rt_uint16_t size) { struct rt_ringbuffer *rb; rt_uint8_t *pool; size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE);// 大小做字节对齐 rb = (struct rt_ringbuffer *)rt_malloc(sizeof(struct rt_ringbuffer));// 申请内存 if (rb == RT_NULL) goto exit; pool = (rt_uint8_t *)rt_malloc(size);// 申请数据缓冲区内存 if (pool == RT_NULL) { rt_free(rb); rb = RT_NULL; goto exit; } rt_ringbuffer_init(rb, pool, size);// 初始化 ringff exit: return rb; } ``` 这个 `API` 简化了使用 `ringbuff` 所需要的 形参。本质还是调用了 `rt_ringbuffer_init` ##### 11. 摧毁 ringbuff ``` void rt_ringbuffer_destroy(struct rt_ringbuffer *rb) { rt_free(rb->buffer_ptr); rt_free(rb);// 释放申请的内存 } ``` ## 四. 总结 1. `ringbuff` 读写数据互不干扰,比普通的 `buff`使用更加灵活。 2. `ringbuff` 的使用场景非常多,可以解决读写速度不一致的问题,RT-Thread 的组件框架中也使用到了,RT-Thread 已经提供了就不需要我们自己再重复造轮子了,这部分代码也很少,想要数量掌握,还是建议读一下源码。
6
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
whj467467222
开源,分享,交流,共同进步
文章
32
回答
1222
被采纳
148
关注TA
发私信
相关文章
1
大量接收数据 如何处理能减少丢包率
2
有没有一种实现类似 ringbuffer, 但返回数据长度 和传入时候一样
3
使用ringbuffer时,自检到thread的type不匹配
4
对ringbuffer中rt_ringbuffer_put_force函数的疑问
5
RINGBUFF里镜像索引取反是为什么?
6
系统提供环形FIFO写入重载时可能存在数据错乱问题
7
rt_ringbuffer_peak疑问
8
串口DMA接收回调函数不起作用
9
串口 DMA ringbuffer 接收有可能覆盖数据
10
rt_ringbuffer_init在buffer_size赋值是否有问题?
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
SFUD
msh
rt_mq_消息队列_msg_queue
keil_MDK
ulog
MicroPython
C++_cpp
本月问答贡献
出出啊
1517
个答案
342
次被采纳
小小李sunny
1443
个答案
289
次被采纳
张世争
805
个答案
174
次被采纳
crystal266
547
个答案
161
次被采纳
whj467467222
1222
个答案
148
次被采纳
本月文章贡献
出出啊
1
篇文章
4
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
1
次点赞
crystal266
2
篇文章
2
次点赞
whj467467222
2
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部