-
2021-07-15 18:46:05
来源:微信公众号「编程学习基地」文章目录
绪论
设计缘由
工作后第一个项目是实现一个数据转发服务器,将视频数据通过websocket转发给手机app端。
第一次在需要考虑网络因素,需要设计一个消息缓冲队列,用于存储大量数据。领导建议我利用环形缓冲区替换链式缓冲区减少memmove的次数,提高服务器性能。
为什么要设计环形缓冲区
目的:避免频繁的内存创建取消、分配,移动以提高服务器性能。内存一直只用了一块。
环形缓冲区的实现原理
环形缓冲区通常有一个读指针和一个写指针。读指针指向环形缓冲区中可读的数据,写指针指向环形缓冲区中可写的缓冲区。通过移动读指针和写指针就可以实现缓冲区的数据读取和写入。在通常情况下,环形缓冲区的读用户仅仅会影响读指针,而写用户仅仅会影响写指针。如果仅仅有一个读用户和一个写用户,那么不需要添加互斥保护机制就可以保证数据的正确性。如果有多个读写用户访问环形缓冲区,那么必须添加互斥保护机制来确保多个用户互斥访问环形缓冲区。
图1、图2和图3是一个环形缓冲区的运行示意图。图1是环形缓冲区的初始状态,可以看到读指针和写指针都指向第一个缓冲区处;图2是向环形缓冲区中添加了一个数据后的情况,可以看到写指针已经移动到数据块2的位置,而读指针没有移动;图3是环形缓冲区进行了读取和添加后的状态,可以看到环形缓冲区中已经添加了两个数据,已经读取了一个数据。
判断“空”和“满”
上述的操作并不复杂,不过有一个小小的麻烦:空环和满环的时候,R和W都指向同一个位置!这样就无法判断到底是“空”还是“满”。
大体上有两种方法可以解决该问题。
办法1:始终保持一个元素不用
当空环的时候,R和W重叠。当W比R跑得快,追到距离R还有一个元素间隔的时候,就认为环已经满。当环内元素占用的存储空间较大的时候,这种办法显得很土(浪费空间)。
办法2:维护额外变量
如果不喜欢上述办法,还可以采用额外的变量来解决。比如可以用一个整数记录当前环中已经保存的元素个数(该整数>=0)。当R和W重叠的时候,通过该变量就可以知道是“空”还是“满”。环形缓冲区设计分两种模式
模式一
写入读取数据,不考虑读取数据的长度,读取数据的顺序为写入数据的顺序
环形缓冲区测试代码
#include "lwsBuffer.h" #include <stdio.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main() { char dataBuf[1024]; void *temp = NULL; int readFd = open("lwsBuffer.cpp", O_RDONLY); int writeFd = open("temp.cpp", O_WRONLY | O_CREAT); int ret; memset(dataBuf, '\0', 1024); lwsBuffer Buff; while (1) { /* code */ memset(dataBuf, '\0', sizeof(dataBuf)); ret = read(readFd, dataBuf, sizeof(dataBuf)); if (ret == 0) { printf("read end.."); break; } else if (ret < 0) printf("read error ret:%d", ret); else printf("read ret:%d\n", ret); ret = Buff.write(dataBuf, ret); //将 dataBuf 数据写入到Buff缓冲区 printf("write ret:%d\n", ret); } printf("\n\ncurrent Buff total len:%d\n\n", Buff.getTotalLen()); while (1) { char tempBuf[1024]; memset(tempBuf,'\0',sizeof(tempBuf)); ret = Buff.read(&temp); //从 Buff缓冲区中读取数据 if (ret == -1) break; memmove(tempBuf, temp, ret); /* code */ printf("total len:%d\n", Buff.getTotalLen()); write(writeFd, tempBuf, ret); } close(readFd); close(writeFd); return 0; }
makefile编译文件
test:test.cpp g++ -o test test.cpp lwsBuffer.cpp .PHONY:clean clean: rm -f test temp.cpp
运行结果
sh-4.3$ make g++ -o test test.cpp lwsBuffer.cpp sh-4.3$ ls lwsBuffer.cpp lwsBuffer.h makefile test test.cpp sh-4.3$ ./test read ret:1024 write ret:1024 read ret:1024 write ret:1024 read ret:1024 write ret:1024 read ret:964 write ret:964 read end.. current Buff total len:4036 total len:3012 total len:1988 total len:964 total len:0 sh-4.3$ ls lwsBuffer.cpp lwsBuffer.h makefile temp.cpp test test.cpp
执行后结果就是读取
lwsBuffer.cpp
里面的数据,储存到环形缓冲区,再从环形缓冲区读取数据写入到temp.cpp
应用场景和优缺点
大量数据的转发
优点:牺牲少量内存实现最少的数据拷贝memmove
缺点:当转发的数据很大时,没一个对象浪费的内存在转发的数据大小之内
当读取数据缓慢的时候会造成频繁的内存重分配,缓冲区变得越来越大。
lwsBuffer.h
#pragma once #ifndef _LWS_BUFFER_H_ #define _LWS_BUFFER_H_ #include <iostream> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <list> #define BUFFER_SIZE 1024 class lwsBuffer { private: /* data */ void* buffer; /* 数据 */ size_t bufMaxSize; /* 最大的存储数据大小 */ size_t rightWritePos; int readpos,writepos; /* 读写位置 */ size_t totalLen; /* 存储的数据大小 */ std::list<int> lenList; /* 数据长度的链表 */ public: lwsBuffer(/* args */); ~lwsBuffer(); public: int write(void *data, int dataLen); int read(void** data); int getTotalLen(); protected: int writeAfter(void* data, int dataLen); int writePre(void* data, int dataLen); void _remalloc(); }; #endif
lwsBuffer.cpp
#include "lwsBuffer.h" lwsBuffer::lwsBuffer(/* args */) { this->readpos = 0; this->writepos = 0; this->rightWritePos = 0; this->bufMaxSize = BUFFER_SIZE; this->buffer = malloc(this->bufMaxSize); this->totalLen = 0; this->lenList.clear(); } lwsBuffer::~lwsBuffer() { if (buffer) free(buffer); } int lwsBuffer::write(void *data, int dataLen) { int ret = -1; if (dataLen <= 0 || data == NULL) return ret; /* writePos 在 readPos 前 / readPos == writePos push*/ if (this->writepos >= this->readpos) { ret = writePre(data, dataLen); } /* readPos 在 writePos 前 push*/ else if(this->writepos < this->readpos) { ret = writeAfter(data, dataLen); } return ret; } int lwsBuffer::writePre(void* data, int dataLen) { int ret = -1; /* 写入位置在前 */ /* buffer 后面有空间可以写入数据*/ if ((this->writepos + dataLen) <= this->bufMaxSize) { //将数据写入到 buffer 里面去 memmove((char *)this->buffer + this->writepos, data, dataLen); /* 修改 writepos 偏移 */ this->writepos = this->writepos + dataLen; /* 计算数据长度*/ lenList.push_back(dataLen); totalLen += dataLen; ret = dataLen; } else { /* buffer 后面没有空间可以写入数据 记录下罪*/ this->rightWritePos = this->writepos; /* 修改 writepos 偏移 */ this->writepos = 0; ret = writeAfter(data, dataLen); } return ret; } int lwsBuffer::writeAfter(void *data, int dataLen) { int ret = -1; /* 写入位置在后 */ /* writepos 到 readpos 有足够的空间可以写入数据 */ if ((this->writepos + dataLen) < this->readpos) { //将数据写入到 buffer 里面去 memmove((char *)this->buffer + this->writepos, data, dataLen); /* 修改 writepos 偏移 */ this->writepos = this->writepos + dataLen; /* 计算数据长度*/ lenList.push_back(dataLen); totalLen += dataLen; ret = dataLen; } else { this->_remalloc(); ret = writePre(data, dataLen); } return ret; } void lwsBuffer::_remalloc() { /* 数据满了 */ void *newBuf = malloc(this->bufMaxSize + BUFFER_SIZE / 2); /* */ memmove(newBuf, (char*)this->buffer + this->readpos, this->rightWritePos - this->readpos); memmove((char*)newBuf + this->rightWritePos - this->readpos, this->buffer, this->writepos); free(this->buffer); this->buffer = newBuf; this->writepos = this->rightWritePos - this->readpos + this->writepos; this->readpos = 0; this->bufMaxSize = this->bufMaxSize + BUFFER_SIZE / 2; // printf("buffMaxSize:%d, totalLen:%d ,list size:%d\n", (int)this->bufMaxSize, (int)this->totalLen, (int)lenList.size()); } int lwsBuffer::read(void** data) { int ret = -1; if (this->totalLen > 0) { int dataLen = lenList.front(); if((this->readpos + dataLen) <= this->bufMaxSize) { (*data) = (char *)this->buffer + this->readpos; // printf("\n读取位置: %d ,读取信息 len: %d, data:%s\n", this->readpos, dataLen, (char*)data); /* 修改 writepos 偏移 */ this->readpos += dataLen; /* 计算数据长度*/ this->totalLen -= dataLen; lenList.pop_front(); ret = dataLen; } else{ printf("从头开始读取\n"); this->readpos = 0; (*data) = (char *)this->buffer + this->readpos; // printf("\n读取位置: %d ,读取信息 len:%d, data:%s\n", this->readpos, dataLen, (char*)data); this->readpos += dataLen; /* 计算数据长度*/ this->totalLen -= dataLen; lenList.pop_front(); ret = dataLen; } } return ret; } int lwsBuffer::getTotalLen() { return this->totalLen; }
模式二
写入读取数据,写入指定长度数据,和读取指定长度数据,模仿QBuffer的读写功能
环形缓冲区测试代码
#include "lwsBuffer.h" #include<stdio.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main() { char buf[1024],temp[1024]; int readFd = open("lwsBuffer.cpp",O_RDONLY); int writeFd = open("temp.cpp",O_WRONLY|O_CREAT,0666); int ret; memset(buf, '\0', 1024); memset(temp, '\0', 1024); lwsBuffer Buff; while (1) { /* code */ ret = read(readFd, buf, sizeof(buf)); if (ret == 0) break; int dataLen = Buff.write(buf,ret); //将数据保存到环形缓冲区 memset(buf, '\0', 1024); printf("dataLen:%d\n",dataLen); } while(1) { memset(temp,'\0',sizeof(temp)); ret = Buff.read(temp, 1024); //从环形缓冲区中读取数据 if (ret == -1) break; /* code */ ret = write(writeFd, temp, ret); printf("write ret:%d\n", ret); } close(readFd); close(writeFd); return 0; }
makefile编译文件
test:test.cpp g++ -o test test.cpp lwsBuffer.cpp .PHONY:clean clean: rm -f test temp.cpp
运行结果
sh-4.3$ make g++ -o test test.cpp lwsBuffer.cpp sh-4.3$ ./test dataLen:1024 dataLen:1024 dataLen:1024 dataLen:766 write ret:1024 write ret:1024 write ret:1024 write ret:766
应用场景和优缺点
适用于少量数据的频繁读写
优点:多次memmove实现占空间最少的快速读写
缺点:当转发的数据很大且很频繁时,多次memmove会导致性能的减少
lwsBuffer.h
#pragma once #ifndef _LWS_BUFFER_H_ #define _LWS_BUFFER_H_ #include <iostream> #include <stdlib.h> #include <stdio.h> #include <string.h> #define BUFFER_SIZE 1024 class lwsBuffer { private: /* data */ void* buffer; /*数据*/ size_t bufMaxSize; /* 最大的存储数据大小 */ size_t bufLen; /* 存储的数据大小 */ int readpos,writepos; /* 读写位置 */ public: lwsBuffer(/* args */); ~lwsBuffer(); public: int write(void *data, int dataLen); int read(void *data, int dataLen); protected: int writeAfter(void* data, int dataLen); int writePre(void* data, int dataLen); void _remalloc(); }; #endif
lwsBuffer.cpp
#include "lwsBuffer.h" lwsBuffer::lwsBuffer(/* args */) { this->readpos = 0; this->writepos = 0; this->bufLen = 0; this->bufMaxSize = BUFFER_SIZE; this->buffer = malloc(this->bufMaxSize); } lwsBuffer::~lwsBuffer() { if (buffer) free(buffer); } int lwsBuffer::write(void *data, int dataLen) { int ret = -1; /* writePos 在 readPos 前 / readPos == writePos push*/ if (this->writepos >= this->readpos) { ret = writePre(data, dataLen); } /* readPos 在 writePos 前 push*/ else if(this->writepos < this->readpos) { ret = writeAfter(data, dataLen); } return ret; } int lwsBuffer::writePre(void* data, int dataLen) { int ret = -1; /* 写入位置在前 */ /* buffer 后面有空间可以写入数据 预留一个字节不存储数据,用于区分*/ if ((this->writepos + dataLen) <= this->bufMaxSize) { //将数据写入到 buffer 里面去 memmove((char *)this->buffer + this->writepos, data, dataLen); // printf("即将 push %d 消息 on:%d :%s\n", dataLen, prod->writepos, (char *)prod->buffer + prod->writepos + LWS_PRE); this->writepos = this->writepos + dataLen; ret = dataLen; bufLen += dataLen; } else { /* buffer 后面没有空间可以写入数据 记录下*/ int temp = this->bufMaxSize - this->writepos; temp = writePre(data, temp); this->writepos = 0; ret = writeAfter((char*)data + temp, dataLen - temp) + temp; } return ret; } int lwsBuffer::writeAfter(void *data, int dataLen) { int ret = -1; /* 写入位置在后 */ /* writepos 到 readpos 有足够的空间可以写入数据 */ if ((this->writepos + dataLen) < this->readpos) { //将数据写入到 buffer 里面去 memmove((char *)this->buffer + this->writepos, data, dataLen); this->writepos = this->writepos + dataLen; ret = dataLen; bufLen += dataLen; } else { this->_remalloc(); ret = writePre(data, dataLen); } return ret; } void lwsBuffer::_remalloc() { /* 数据满了 */ void *newBuf = malloc(this->bufMaxSize + BUFFER_SIZE / 2); /* */ memmove(newBuf, (char*)this->buffer + this->readpos, this->bufMaxSize - this->readpos); memmove((char*)newBuf + this->bufMaxSize - this->readpos, this->buffer, this->writepos); free(this->buffer); this->buffer = newBuf; this->writepos = this->bufMaxSize - this->readpos + this->writepos; this->readpos = 0; this->bufMaxSize = this->bufMaxSize + BUFFER_SIZE / 2; // printf("buffMaxSize:%d, bufLen:%d\n", (int)this->bufMaxSize,(int)this->bufLen); } int lwsBuffer::read(void *data, int dataLen) { int ret = -1; if (this->bufLen > 0) { if (dataLen > this->bufLen) { dataLen = this->bufLen; } if ((this->readpos + dataLen) <= this->bufMaxSize) { memmove(data, (char *)this->buffer + this->readpos, dataLen); // printf("\n读取位置: %d ,读取信息 len:%d,data:%s\n", this->readpos, dataLen, (char *)data); this->readpos += dataLen; this->bufLen -= dataLen; ret = dataLen; } else { int temp = this->bufMaxSize - this->readpos; temp = read(data, temp); this->readpos = 0; memmove((char*)data + temp, (char *)this->buffer, dataLen - temp); // printf("\n读取位置: %d ,读取信息 len:%d, data:%s\n", this->readpos, dataLen, (char *)data); this->readpos = this->readpos + dataLen - temp; this->bufLen = this->bufLen - (dataLen - temp); ret = dataLen; } } return ret; }
更多相关内容 -
C 语言中实现环形缓冲区
2020-09-02 01:35:49本文主要是介绍 C语言实现环形缓冲区,并附有详细实现代码,具有一定的参考价值,希望能帮助有需要的小伙伴 -
golang-ring:在Golang中实现的简单环形缓冲区类型
2021-05-27 21:20:12打包环提供了环形缓冲区的简单实现。 用法 var DefaultCapacity int = 10 未初始化的环形缓冲区的DefaultCapacity。 更改此值只会影响更改后创建的环形缓冲区。 环型 type Ring struct { sync. Mutex } 类型... -
基于环形缓冲区的CAN驱动模块
2021-02-03 13:56:53基于环形缓冲区的CAN驱动模块、电子技术,开发板制作交流 -
环形缓冲区
2017-11-07 20:49:56环形缓冲区的优点就不赘述了,此VS2015工程封装好环形缓冲区的接口,Boost库的二次封装,有测试示例。如果有编译问题,选择x86编译,记得要配置boost库环境。PS:如果可以的话,大量使用boost库,用起来很方便。 -
基于STM32FxUART数据传输环形缓冲区实现
2020-05-08 11:53:50该工程包含了整个实现代码,并添加了注释,提供了软件计时器多任务创建及调度接口函数,以及环形缓冲区完整接口函数。 -
C#环形缓冲区(队列)完全实现
2020-09-01 22:47:32主要为大家详细介绍了C#环形缓冲区(队列)完全实现代码,感兴趣的小伙伴们可以参考一下 -
易版环形缓冲区 理论上支持多线程-易语言
2021-06-11 16:02:34假设在环形缓冲区的中心写入1(确切的起始位置在环形缓冲区中并不重要): 然后,假设将另外两个字节(23)添加到环形缓冲区,它们将放在1之后: 如果删除了两个字节,则环形缓冲区内部的两个最早加入的值将被删除。 ... -
ringbuffer:Go中的一个线程安全的循环缓冲区(环形缓冲区),实现了io.ReaderWriter接口
2021-05-08 12:02:36环形缓冲区 Go中的循环缓冲区(环形缓冲区),已实现io.ReaderWriter接口 rb := New ( 1024 ) // write rb . Write ([] byte ( "abcd" )) fmt . Println ( rb . Length ()) fmt . Println ( rb . Free ()) //... -
C语言环形缓冲区
2017-03-10 20:29:31C语言实现环形缓冲区,可供多线程读写操作 -
基于VxWorks的环形缓冲机制的驱动设计
2021-01-28 17:58:38分析了VxWorks嵌入式操作系统的驱动程序功能设计,以多串口通信驱动为例,结合看门狗定时器和中断处理机制,采用VxWorks系统自身提供的环形缓冲区管理接口库函数,设计了一种更为可靠高效、性能稳定的字符设备底层... -
STM32进阶之串口环形缓冲区实现
2018-10-01 11:44:46这种处理方式是没有缓冲区的,当数量太大的时候,亦或者当数据接收太快的时候,我们来不及处理已经收到的数据, 那么,当再次收到数据的时候,就会将之前还未处理的数据覆盖掉。那么就会出现丢包的现象了,对我们的... -
STM32进阶之串口环形缓冲区实现 FIFO
2019-01-26 13:38:13STM32进阶之串口环形缓冲区实现 FIFO,代码精简,易实现。 -
ringbuf:Go 的线程安全环形缓冲区实现
2021-07-01 18:34:45环缓冲 Ringbuf 是 go 语言的 ringbuffer 的简单无锁实现。 -
总是看起来像平面数组的C ++环形缓冲区-C/C++开发
2021-05-27 01:31:03内容尽管有名称,但该存储库包含两个不同的环形缓冲区的实现:线性环形缓冲区:include / bev / linear_ringbuffer.hpp IO缓冲区:include / bev / io_buffer.hpp此顶级自述文件主要由目录提供。尽管有名称,该存储... -
RingBuffer_环形缓冲区_
2021-10-02 08:50:37能够实现队列和堆栈操作,支持任意数据类型。 -
环形缓冲测试.rar
2019-07-24 09:14:34STM32F103ZET6芯片,配置串口,创建环形缓冲,通过串口收发,可以做到无丢包,STM32F103ZET6芯片,配置串口,创建环形缓冲,通过串口收发,可以做到无丢包,STM32F103ZET6芯片,配置串口,创建环形缓冲,通过串口... -
Go-Go中的线程安全循环缓冲区环形缓冲区实现了io.ReaderWriter接口
2019-08-14 02:34:47Go中的线程安全循环缓冲区(环形缓冲区),实现了io.ReaderWriter接口 -
ringbuf-可直接访问内部数据的无锁SPSC FIFO环形缓冲区-Rust开发
2021-05-27 19:23:03ringbuf无锁单生产者单消费者(SPSC)FIFO环形缓冲区,可直接访问内部数据。 概述RingBuffer是最初的结构ringbuf无锁单生产者单消费者(SPSC)FIFO环形缓冲区,可直接访问内部数据。 概述RingBuffer是代表环形缓冲区... -
环形缓冲区实现类(RingBuffer)
2015-09-01 12:49:27环形缓冲区实现类(RingBuffer) -
实时系统vxWorks - 环形缓冲
2021-08-18 01:00:56概述Vxwroks 环形缓冲模块主要定义在rngLib.c和rngLib.h中,对于数据结构比较了解的小伙伴应该知道,环形缓冲实际就是一个双向循环队列。注意关于循环队列,小编之前在《也没想...概述
Vxwroks 环形缓冲模块主要定义在rngLib.c和rngLib.h中,对于数据结构比较了解的小伙伴应该知道,环形缓冲实际就是一个双向循环队列。
注意
关于循环队列,小编之前在《也没想象中那么神秘的数据结构-先来后到的“队列”(循环队列)》一文中有过详细阐述,这里就不在过多的赘述了。
开发环境:vxWorks6.9.4,workbench3.3.5,开发板:TLZ7x-EasyEVM-A3。
另外,小编所有文章均是自己亲手编写验证,若需要小编的工程代码,请关注公众号,后台回复需要的工程文件。如想要本文中的工程源文件可回复“实时系统vxWorks - 环形缓冲工程文件”获取。
以下为工程目录文件内容。有需要的小伙伴后台发送相关信息给小编获取。
文件内容如下:
obj:存放目标文件,包含vxWorks镜像,应用程序目标文件。
rng_test:vxWorks应用工程。
接口
官方接口
官方环形缓冲接口定义主要包含在rngLib.h头文件中。
环形缓冲接口定义
typedef struct /* RING - ring buffer */ { size_t pToBuf; /* 写指针 */ size_t pFromBuf; /* 读指针 */ size_t bufSize; /* 数据长度 */ char *buf; /* 存放数据 */ } RING; /* END_HIDDEN */ typedef RING *RING_ID;
创建/删除环形缓冲
/** * @创建环形缓冲 * @nbytes: 缓冲尺寸 * @成功返回环形缓冲ID,失败返回NULL。 */ extern RING_ID rngCreate (size_t nbytes); /** * @删除环形缓冲 * @ringId: 环形缓冲ID */ extern void rngDelete (RING_ID ringId);
判断缓冲是否空/满
/** * @判断环形缓冲是否为空 * @ringId: 环形缓冲ID * @不为空返回0,否则返回其它。 */ extern BOOL rngIsEmpty (RING_ID ringId); /** * @判断环形缓冲是否为满 * @ringId: 环形缓冲ID * @不为满返回0,否则返回其它。 */ extern BOOL rngIsFull (RING_ID ringId);
读写数据
/** * @从环形缓冲中读取数据 * @ringId: 环形缓冲ID buffer:数据 maxbytes:长度 * @返回实际读取到的数据长度。 */ extern size_t rngBufGet (RING_ID rngId, char *buffer, size_t maxbytes); /** * @写数据到环形缓冲 * @ringId: 环形缓冲ID buffer:数据 nbytes:长度 * @返回实际写入数据长度 */ extern size_t rngBufPut (RING_ID rngId, char *buffer, size_t nbytes);
获取缓冲区剩余空间/已占空间
/** * @获取环形缓冲剩余空间长度 * @ringId: 环形缓冲ID * @返回剩余空间长度。 */ extern size_t rngFreeBytes (RING_ID ringId); /** * @获取环形缓冲已占空间长度 * @ringId: 环形缓冲ID * @返回已占空间长度。 */ extern size_t rngNBytes (RING_ID ringId);
清空缓冲
/** * @清空环形缓冲 * @ringId: 环形缓冲ID */ extern void rngFlush (RING_ID ringId);
环形缓冲对象接口
属性定义
/* 环形缓冲类 */ struct t_rng { u8_t flag; /* 创建标志,=1已创建,=0未创建 */ RING_ID id; /* 环形缓冲 */ struct t_sem msem; /* 环形缓冲互斥信号量 */ };
保存数据到环形缓冲
/** * @保存数据到环形缓冲 * @p_rng:环形缓冲类 buf: 数据 len:长度 **/ void put_rng(struct t_rng *p_rng, s8_t *buf, s32_t len);
获取环形缓冲数据
/** * @获取环形缓冲数据 * @p_rng:环形缓冲类 buf装载数据, len数据长度 * 成功返回实际数据长度,失败返回ERROR **/ s32_t get_rng(struct t_rng *p_rng, s8_t *buf, s32_t len);
创建环形缓冲
/** * @创建环形缓冲 * @p_rng:环形缓冲类 nbyte申请缓冲大小, name互斥信号名 **/ void create_rng(struct t_rng *p_rng, size_t nbytes, s8_t *name);
示例
★示例创建两个任务(生产者/消费者),生产者向缓冲写入数据,消费者从缓冲读取数据,缓冲访问时使用互斥信号量进行互斥。
★关于信号量和任务部分可参加小编的《实时系统vxWorks - 信号量(重要)》和《实时系统vxWorks - 任务(重要)》文章。
★包含环形缓冲类rng.c/rng.h和演示程序main.c(已验证通过)。
rng.h
/** * @Filename : rng.h * @Revision : $Revision: 1.00 $ * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅) * @Description : 环形缓冲类,在系统自带环形缓冲基础上封装互斥信号量 **/ #ifndef __RNG_CLASS_H__ #define __RNG_CLASS_H__ #include <vxWorks.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <rngLib.h> #include "sem.h" extern struct sem sem; /* 保存系统信号量信息 */ /* 环形缓冲类 */ struct t_rng { u8_t flag; /* 创建标志,=1已创建,=0未创建 */ RING_ID id; /* 环形缓冲 */ struct t_sem msem; /* 环形缓冲互斥信号量 */ }; /** * @保存数据到环形缓冲 * @p_rng:环形缓冲类 buf: 数据 len:长度 **/ void put_rng(struct t_rng *p_rng, s8_t *buf, s32_t len); /** * @获取环形缓冲数据 * @p_rng:环形缓冲类 buf装载数据, len数据长度 * 成功返回实际数据长度,失败返回ERROR **/ s32_t get_rng(struct t_rng *p_rng, s8_t *buf, s32_t len); /** * @创建环形缓冲 * @p_rng:环形缓冲类 nbyte申请缓冲大小, name互斥信号名 **/ void create_rng(struct t_rng *p_rng, size_t nbytes, s8_t *name); #endif
rng.c
/** * @Filename : rng.c * @Revision : $Revision: 1.00 $ * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅) * @Description : 环形缓冲类,在系统自带环形缓冲基础上封装互斥信号量 **/ #include "rng.h" /** * @保存数据到环形缓冲 * @p_rng:环形缓冲类 buf: 数据 len:长度 **/ void put_rng(struct t_rng *p_rng, s8_t *buf, s32_t len) { if (p_rng->flag == 0) return; get_sem(&p_rng->msem); if (!(rngIsFull(p_rng->id))) rngBufPut(p_rng->id, buf, len); lose_sem(&p_rng->msem); } /** * @获取环形缓冲数据 * buf装载数据, len数据长度 * 成功返回实际数据长度,失败返回ERROR **/ s32_t get_rng(struct t_rng *p_rng, s8_t *buf, s32_t len) { s32_t rLen = ERROR; if (p_rng->flag == 0) return ERROR; get_sem(&p_rng->msem); if (!(rngIsEmpty(p_rng->id))) rLen = rngBufGet(p_rng->id, buf, len); lose_sem(&p_rng->msem); return (rLen); } /** * @创建环形缓冲 * nbyte申请缓冲大小, name互斥信号名 **/ void create_rng(struct t_rng *p_rng, size_t nbytes, s8_t *name) { if (p_rng->flag == 1) return; p_rng->id = rngCreate(nbytes); p_rng->msem.sem = NULL; p_rng->msem.type = SEM_MUTEX; strcpy(p_rng->msem.name, name); resgister_sem(&sem, &p_rng->msem); p_rng->flag = 1; }
main.c
/** * @Filename : main.c * @Revision : $Revision: 1.00 $ * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅) * @Description : 环形缓冲类使用示例 **/ #include <vxWorks.h> #include <math.h> #include "stdioLib.h" #include "strLib.h" #include "task.h" #include "sem.h" #include "rng.h" #include "feng_type.h" #define RNG_SIZE 100 /* 缓冲大小 */ struct sem sem; /* 保存系统信号量信息 */ struct t_task s_task, s_task1; struct t_sem s_sem, s_sem1; struct t_rng rng; /** * @生产者:定时存储数据到环形缓冲 **/ static void _thread(void) { static int cnt = 77; while (1) { get_sem(&s_sem); put_rng(&rng, &cnt, sizeof(int)); printf("put data : %d...\n", cnt++); } } /** * @消费者:定时从环形缓冲中取出数据 **/ static void _thread1(void) { int cnt1 = 0; while (1) { get_sem(&s_sem1); if (get_rng(&rng, &cnt1, sizeof(int)) > 0) printf("get data : %d...\n", cnt1); } } /** * @创建任务 * @p_task:任务类 name:任务名 thread:函数 **/ void _create_task(struct t_task *p_task, char *name, FUNCPTR thread) { strcpy(p_task->name, name); p_task->options = VX_FP_TASK; p_task->stackSize = 50 * 1024; p_task->pFunc = thread; p_task->tid = NULL; p_task->core = 0; p_task->priority = 102; create_task(p_task); start_task(p_task); } /** * @创建信号量 * @p_sem:信号量类 name:信号量名 type:类型 **/ void _create_sem(struct t_sem *p_sem, char *name, E_SEM_TYPE type) { strcpy(p_sem->name, name); p_sem->period = 100; p_sem->type = type; p_sem->sem = SEM_ID_NULL; resgister_sem(&sem, p_sem); } int main(void) { sysClkRateSet(100); /* 时间片设置 */ create_rng(&rng, RNG_SIZE, "my_rng"); _create_sem(&s_sem, "my_sem", SEM_BIN); _create_sem(&s_sem1, "my_sem1", SEM_BIN); _create_task(&s_task, "my_task", (FUNCPTR)_thread); _create_task(&s_task1, "my_task1", (FUNCPTR)_thread1); while (1) { taskDelay(500); /* 5s */ lose_sem_by_name(&sem, "my_sem"); lose_sem_by_name(&sem, "my_sem1"); } return 0; }
验证
使用环形缓冲之前需要先添加INCLUDE_RNG_BUF组件。
打开镜像工程,选择kernel Configuration。按住Ctrl+F,输入rng,找到ring buffers,添加组件。
添加完组件后,编译镜像,将镜像拷贝到目标机加载指定目录。
创建应用工程rng_test,输入相关测试代码,运行后如下图所示。
注意:若不知道工程如何创建以及运行,可参见小编文章《实时系统vxWorks - 任务(重要)》和《实时系统vxWorks - 加载应用程序的方法》。
往期 · 推荐
关注
更多精彩内容,请关注微信公众号:不只会拍照的程序猿,本人致力分享linux、设计模式、C语言、嵌入式、编程相关知识,也会抽空分享些摄影相关内容,同样也分享大量摄影、编程相关视频和源码,另外你若想要获得更多内容教程请关注公众号:不只会拍照的程序猿。
-
环形缓冲区实现原理
2013-08-27 09:15:25在通信程序中,经常使用环形缓冲区作为数据结构来存放通信中发送和接收的数据。环形缓冲区是一个先进先出的循环缓冲区,可以向通信程序提供对缓冲区的互斥访问。 -
C语言构建环形缓冲区
2021-08-28 23:55:001、环形缓冲区原理 环列队列逻辑上将数组元素array[0]与array[LEN-1]连接起来,形成一个存放队列的环形空间。实际操作中为了方便读写,采用head和tail分别指向可以读的位置和可以写的位置。 环形它逻辑上是一个首尾相连的FIFO结构,具体实现上采用简单的线性数组。通过额外的辅助标志(head、tail)能很快知道队列的使用情况(是满还是为空)。正因为其简单高效的原因,甚至在硬件都实现了环形队列。
环形队列广泛用于网络数据收发、程序间的大量数据交换(比如内核与应用程)、硬件接收大量数据。
1、环形缓冲区原理
- 环列队列逻辑上将数组元素array[0]与array[LEN-1]连接起来,形成一个存放队列的环形空间。实际操作中为了方便读写,采用head和tail分别指向可以读的位置和可以写的位置。
- 环形队列的关键是判断队列为空,还是为满。一般有两种方法:
- 一是附加一个标志位tag
- 当head赶上tail,队列空,则令tag=0
- 当tail赶上head,队列满,则令tag=1
- 二是在队尾结点与队首结点之间留有1个元素的空间
- 队列空: head==tail
- 队列满: (tail+1)% MAXN ==head
2、预留1个空位的环形队列
- 开始时head和tail指向同一个地址,但随着数据的push和poll,head和tail之间永远预留一个单元空间。如下图所示即为一个满队列。但箭头所指的位置不准确。tail应该指向空位,head指向9。即从头出去,从尾巴进来。
- 数据结构:
struct ring_queue{ unsigned int head; unsigned int tail; unsigned int size; //环形缓冲区容量 int *array; //实际缓冲区(数组)的首地址 };
- 规则:
- head指向可读位置(地址存有数据),tail指向可写位置(地址无数据)。
- 初始化状态: head = tail = 0;
- 判定队列为空:head == tail
- 队列为满:**( (tail+1) % SIZE ) == head **
- 入队操作:若队列不满,则写入。
- 出队操作:若队列不空,则读出。
- 缓冲区必须是连续的内存空间,可以通过静态数组变量或局部数组变量的方式定义,但绝不能从堆上分配(malloc),因为malloc分配的内存空间是不连续的!!!
- 头文件:
#ifndef __RINGQ_H__ #define __RINGQ_H__ #ifdef __cplusplus extern "C" { #endif typedef struct { unsigned int head; unsigned int tail; unsigned int size; int *array; }RINGQ; #define ringq_is_empty(q) (q->head == q->tail) #define ringq_is_full(q) (((q->tail+1)%q->size) == q->head ) int ringq_init(RINGQ * ringqp, int * array_ptr, unsigned size); int ringq_free(RINGQ * ringqp); int ringq_push(RINGQ * ringqp,int data); int ringq_poll(RINGQ * ringqp,int * val); void ringq_display(RINGQ * ringqp); #ifdef __cplusplus } #endif #endif
- c文件:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include "ringq.h" 4 5 int ringq_init(RINGQ * ringqp, int * array_ptr, unsigned size) 6 { 7 /* 8 不能从堆里分配空间!!! 9 因为堆里的空间不是连续的,而是通过链表链接的一个空间串 10 if(!(ringqp->array = (int *)malloc(size))){ 11 printf("Malloc failed!\n"); 12 return -1; 13 } 14 */ 15 ringqp->array = array_ptr; 16 ringqp->size = size; 17 ringqp->head = 0; 18 ringqp->tail = 0; 19 return 0; 20 } 21 22 int ringq_free(RINGQ * ringqp) 23 { 24 free(ringqp->array); 25 return 0; 26 } 27 28 29 int ringq_push(RINGQ * ringqp,int data) 30 { 31 if(ringq_is_full(ringqp)) 32 { 33 printf("ringq is full.\n"); 34 return -2; 35 } 36 ringqp->array[ringqp->tail] = data; 37 ringqp->tail = (ringqp->tail + 1) % ringqp->size ; 38 return 0; 39 } 40 41 42 int ringq_poll(RINGQ * ringqp,int * val) 43 { 44 if(ringq_is_empty(ringqp)) 45 { 46 printf("ringq is empty.\n"); 47 return -3; 48 } 49 *val = ringqp->array[ringqp->head]; 50 ringqp->head = (ringqp->head + 1) % ringqp->size ; 51 return 0; 52 } 53 54 void ringq_display(RINGQ * ringqp) 55 { 56 int i =0; 57 unsigned head = ringqp->head; 58 unsigned tail = ringqp->tail; 59 unsigned size = ringqp->size; 60 61 if(ringq_is_empty(ringqp)) 62 { 63 printf("ringq is empty.\n"); 64 return; 65 } 66 while(head != tail){ 67 printf("%04d ",ringqp->array[head]); 68 i++; 69 if(i == 5){ 70 printf("\n"); 71 i = 0; 72 } 73 head = (head + 1)%(size); 74 } 75 printf("\n"); 76 return; 77 }
- 测试文件:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include "ringq.h" 4 5 #define RQ_SIZE 50 6 7 int main(void) 8 { 9 int data_in = 0; 10 int data_out = 0; 11 int select = 0; 12 13 int array[RQ_SIZE]={}; 14 RINGQ rq, *rqp; 15 rqp = &rq; 16 17 ringq_init(rqp, array, RQ_SIZE); 18 ringq_display(rqp); 19 20 int index = RQ_SIZE - 1; //allways a bank between head and tail 21 while (index > 0){ 22 ringq_push(rqp,1); 23 index -= 1; 24 } 25 26 ringq_display(rqp); 27 28 while (index < RQ_SIZE-1){ 29 ringq_poll(rqp,&data_out); 30 index += 1; 31 } 32 33 ringq_display(rqp); 34 35 while (index > 0){ 36 ringq_push(rqp,2); 37 index -= 1; 38 } 39 40 ringq_display(rqp); 41 42 return 0; 43 }
- 实验结果:
- 测试文件2
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include "ringq.h" 4 5 #define RQ_SIZE 5 6 7 int main(void) 8 { 9 int data_in = 0; 10 int data_out = 0; 11 int select = 0; 12 13 int array[RQ_SIZE]={}; 14 RINGQ rq, *rqp; 15 rqp = &rq; 16 17 ringq_init(rqp, array, RQ_SIZE); 18 ringq_display(rqp); 19 42 43 loop: puts("push or poll or quit? [i/o/q]"); 44 select = getchar(); 45 getchar(); //丢弃回车 46 switch(select){ 47 case 'i': 48 printf("The push data is:"); 49 scanf("%d",&data_in); 50 getchar(); //丢弃回车 51 ringq_push(rqp, data_in); 52 break; 53 case 'o': 54 ringq_poll(rqp, &data_out); 55 printf("%d poll successfull.\n",data_out); 56 break; 57 case 'q': 58 59 return 0; 60 default: 61 printf("Wrong choice!enter i or o!\n"); 62 } 63 ringq_display(rqp); 64 printf("\n"); 65 goto loop; 66 }
- 实验结果:
- 有关getchar()和输入缓冲区的关系,请查看一文搞懂getchar()和putchar()的奇怪现象_Leon的博客-CSDN博客或者C语言 getchar()原理及易错点解析_Kevin.wang-CSDN博客_c语言getchar
3、附加满空标志位的环形队列
- 数据结构
typedef struct ringq{ int head; int tail; int tag ; int size ; int space[RINGQ_MAX]; }RINGQ;
- 规则
- 初始化状态: q->head = q->tail = q->tag = 0;
- 队列为空:(q->head == q->tail) && (q->tag == 0)
- 队列为满**: ((q->head == q->tail) && (q->tag == 1))**
- 入队操作:如队列不满,则写入q->tail = (q->tail + 1) % q->size ;
- 出队操作:如果队列不空,则从head处读出。下一个可读的位置在 q->head = (q->head + 1) % q->size
- 头文件
#ifndef __RINGQ_H__ #define __RINGQ_H__ #ifdef __cplusplus extern "C" { #endif #define QUEUE_MAX 20 typedef struct ringq{ int head; int tail; int tag ; int size ; int space[QUEUE_MAX]; }RINGQ; extern int ringq_init(RINGQ * p_queue); extern int ringq_free(RINGQ * p_queue); extern int ringq_push(RINGQ * p_queue,int data); extern int ringq_poll(RINGQ * p_queue,int *p_data); #define ringq_is_empty(q) ( (q->head == q->tail) && (q->tag == 0)) #define ringq_is_full(q) ( (q->head == q->tail) && (q->tag == 1)) #define print_ringq(q) printf("ring head %d,tail %d,tag %d\n", q->head,q->tail,q->tag); #ifdef __cplusplus } #endif #endif
- 实现代码
#include <stdio.h> #include "ringq.h" int ringq_init(RINGQ * p_queue) { p_queue->size = QUEUE_MAX ; p_queue->head = 0; p_queue->tail = 0; p_queue->tag = 0; return 0; } int ringq_free(RINGQ * p_queue) { return 0; } //Write data to the queue int ringq_push(RINGQ * p_queue,int data) { print_ringq(p_queue); if(ringq_is_full(p_queue)) { printf("ringq is full\n"); return -1; } p_queue->space[p_queue->tail] = data; p_queue->tail = (p_queue->tail + 1) % p_queue->size ; if(p_queue->tail == p_queue->head) { p_queue->tag = 1; } return p_queue->tag ; } //Get data from the queue int ringq_poll(RINGQ * p_queue,int * p_data) { print_ringq(p_queue); if(ringq_is_empty(p_queue)) { printf("ringq is empty\n"); return -1; } *p_data = p_queue->space[p_queue->head]; p_queue->head = (p_queue->head + 1) % p_queue->size ; if(p_queue->tail == p_queue->head) { p_queue->tag = 0; } return p_queue->tag ; }
- 测试代码
//请参考上节的test.c
-
lwrb开源库 FIFO轻量级环形缓冲管理器开源库--可嵌入式移植
2021-07-24 00:26:20FIFO轻量级环形缓冲管理器开源库--可嵌入式移植 blog: https://blog.csdn.net/Wekic/article/details/119046393 -
免锁环形缓冲区实现单片机共享数据处理的同步.pdf
2021-07-12 20:25:25免锁环形缓冲区实现单片机共享数据处理的同步.pdf