一个环形缓冲区的使用
·
背景信息
在软件开发中,尤其是在嵌入式系统、实时数据处理以及通信协议的实现中,环形缓冲区(Circular Buffer) 是一种非常高效的数据结构。它以其循环存储的特性,解决了传统线性队列在固定空间内的浪费问题。最近,在处理高频数据传输时,我遇到了以下挑战:
- 数据流的缓冲与传递:需要在生产者与消费者之间缓冲数据,避免数据丢失。
- 有限内存资源的高效利用:系统内存有限,必须尽可能减少分配与释放操作的开销。
- 性能瓶颈:传统队列在频繁操作时效率较低,环形缓冲区的循环队列机制可以显著提升性能。
为了应对这些问题,我实现了一个简单而高效的环形缓冲区,通过合理地控制读写指针,有效地解决了数据流存储和读取的需求,同时避免了内存浪费和性能问题。接下来,我将结合代码示例,详细介绍环形缓冲区的原理与实现。
环形缓冲区指针位置详解
环形缓冲区是一个逻辑上首尾相连的数据存储结构,其核心在于通过写指针和读指针的管理,实现高效的数据读写操作。下面我们具体说明两者的作用。
要点 1:缓冲区写指针位置
写指针(wroffset) 的作用:
- 指向缓冲区中下一次写入数据的位置。
- 写入数据后,指针向前移动写入的字节数。
- 当写指针到达缓冲区尾部时,会通过取模操作循环回到缓冲区的开头。
要点 2:缓冲区读指针位置
读指针(rdoffser) 的作用:
- 指向缓冲区中下一次读取数据的位置。
- 读取数据后,指针向前移动读取的字节数。
- 和写指针一样,当读指针到达缓冲区尾部时,会通过取模操作循环回到缓冲区的开头。
举例说明
1. 创建一个 10 字节大小的缓冲区
假设缓冲区的总大小为 buflen = 10,初始状态如下:
- 写指针位置:
wroffset = 0(指向起始位置)。 - 读指针位置:
rdoffser = 0(指向起始位置)。 - 缓冲区为空,尚未存储任何数据。
图示:
Index: 1 2 3 4 5 6 7 8 9 10
Data: . . . . . . . . . .
R
W
//<<计算可用字节
canWriteLen=(cb_buffer->rdoffser)-(cb_buffer->wroffset);
//<<cb_buffer->rdoffser = 0 cb_buffer->wroffset = 0
if(canWriteLen<=0) canWriteLen+=cb_buffer->buflen;
//<<canWriteLen+=cb_buffer->buflen ; 10字节
2. 写入 3 字节数据
写入数据 0xA, 0xB, 0xC 后:
- 写入从索引
0开始,占用位置0, 1, 2。 - 写指针位置:
wroffset = 3(指向下一次写入的位置)。 - 读指针位置:
rdoffser = 0(未读取数据)。
图示:
Index: 1 2 3 4 5 6 7 8 9 10
Data: A B C . . . . . . .
R
W
3. 读取 2 字节数据
读取数据 0xA, 0xB 后:
- 数据从索引
0, 1读取,占用位置释放。 - 写指针位置:
wroffset = 3(未发生写入)。 - 读指针位置:
rdoffser = 2(指向下一次读取的位置)。
图示:
Index: 1 2 3 4 5 6 7 8 9 10
Data: . . C . . . . . . .
R
W
4. 写入 5 字节数据
写入数据 0xD, 0xE, 0xF, 0xG, 0xH 后:
- 写入从索引
3开始,占用位置3, 4, 5, 6, 7。 - 写指针位置:
wroffset = 8(指向下一次写入的位置)。 - 读指针位置:
rdoffser = 2(未发生读取)。
Index: 1 2 3 4 5 6 7 8 9 10
Data: . . C D E F G H . .
R
W
5. 写入 3 字节数据(跨越尾部)
写入数据 0xI, 0xJ, 0xK 后:
- 第一部分写入到索引
8, 9。 - 第二部分写入到索引
0(环形缓冲区循环)。 - 写指针位置:
wroffset = 1(循环回到缓冲区开头)。 - 读指针位置:
rdoffser = 2(未发生读取)。
图示:
Index: 1 2 3 4 5 6 7 8 9 10
Data: K . C D E F G H I J
R
W
//<<计算可用字节
canWriteLen=(cb_buffer->rdoffser)-(cb_buffer->wroffset);
//<<cb_buffer->rdoffser = 2 cb_buffer->wroffset = 1 canWriteLen = 1
6. 相关代码
//<<环形缓冲区结构体
typedef struct circlebuffer
{
ZUINT32 size; //<<缓冲区可用空间
ZUINT32 wroffset; //<<缓冲区写指针位置
ZUINT32 rdoffser; //<<缓冲区读指针位置
ZUINT8 * buf; //<<缓冲区数据包
ZUINT32 buflen; //<<缓冲区数据包大小
}circleBuffer;
//<<环形缓冲区指针
circleBuffer* cbBuffer[8];
/***********************************************************************
*@函数名称: cb_init
*@功能描述: 初始化环形缓冲区,并为其分配内存;
*@参数:
index[in]: 缓冲区在全局数组中的索引;
bufLen[in]: 环形缓冲区的长度;
*@返回:
circleBuffer*: 成功,返回初始化的环形缓冲区指针;
NULL: 失败,缓冲区长度为 0 或分配失败;
*@备注:
- 在使用此函数前,需确保 cbBuffer 已正确定义并分配空间;
- 调用方需要在不再使用缓冲区时释放分配的内存。
**********************************************************************/
circleBuffer * cb_init(ZUINT32 index,ZUINT32 bufLen)
{
if(bufLen==0)
return NULL;
//<<结构体指针开空间
cbBuffer[index] = (circleBuffer*)malloc(sizeof(circleBuffer));
memset(cbBuffer[index],0,sizeof(circleBuffer));
//<<环形缓冲区开空间
cbBuffer[index]->buflen=bufLen;
cbBuffer[index]->buf=(UCHAR *)malloc(bufLen);
memset(cbBuffer[index]->buf,0,bufLen);
//<<返回开好的结构体指针
return cbBuffer[index];
}
/***********************************************************************
*@函数名称: cb_getcnt
*@功能描述: 获取指定环形缓冲区中当前有效数据的字节数;
*@参数:
index[in]: 缓冲区在全局数组 cbBuffer 中的索引;
*@返回:
ZUINT32: 当前缓冲区中的有效数据字节数;
*@备注:
- 返回值为缓冲区中已写入但尚未读取的数据长度;
- 调用方需确保 index 对应的环形缓冲区已正确初始化。
**********************************************************************/
ZUINT32 cb_getcnt(ZUINT32 index)
{
ZUINT32 dataCnt = 0;
dataCnt = cbBuffer[index]->size;
return dataCnt;
}
/***********************************************************************
*@函数名称: cb_Reset
*@功能描述: 重置指定的环形缓冲区,将其恢复为初始状态;
*@参数:
index[in]: 缓冲区在全局数组 cbBuffer 中的索引;
*@返回: 无;
*@备注:
- 该函数会清空缓冲区中的所有数据,并重置读写指针;
- 缓冲区的长度和分配的内存不会改变;
- 调用方需确保 index 对应的缓冲区已正确初始化。
**********************************************************************/
void cb_Reset(ZUINT32 index)
{
//<<清0
cbBuffer[index]->size = 0;
cbBuffer[index]->rdoffser = 0;
cbBuffer[index]->wroffset = 0;
memset(cbBuffer[index]->buf,0,cbBuffer[index]->buflen);
}
/***********************************************************************
*@函数名称: cb_free
*@功能描述: 释放指定索引的环形缓冲区及其相关资源;
*@参数:
index[in]: 缓冲区在全局数组 cbBuffer 中的索引;
*@返回: 无;
*@备注:
- 该函数会释放环形缓冲区结构体及其内部的缓冲区内存;
- 同时清空与该缓冲区相关的全局数组 RecvData 和 Data 的内存;
- 调用该函数后,index 对应的缓冲区指针将被置为 NULL,防止悬空指针;
- 调用方需确保在释放前,缓冲区及相关数组已正确初始化。
**********************************************************************/
void cb_free(ZUINT32 index)
{
if (cbBuffer[index])
{
if (cbBuffer[index]->buf)
{
free(cbBuffer[index]->buf);
cbBuffer[index]->buf = NULL;
}
free(cbBuffer[index]);
cbBuffer[index] = NULL;
}
//<<清空数组
if( Data[index])
{
free(RecvData[index]);
RecvData[index] = NULL;
}
//<<清空数组
if(Data[index])
{
free(Data[index]);
Data[index] = NULL;
}
}
/***********************************************************************
*@函数名称: cb_write
*@功能描述: 向环形缓冲区写入指定长度的数据;
*@参数:
cb_buffer[in]: 指向目标环形缓冲区的指针;
inbuf[in]: 输入数据的起始地址;
wrireLen[in]: 请求写入的数据长度;
*@返回:
ZUINT32: 成功,返回实际写入的数据字节数;
0: 失败,环形缓冲区已满或输入无效;
-1: 失败,环形缓冲区剩余空间不足;
*@备注:
- 写入操作前会检查缓冲区的剩余空间,确保不会覆盖未读数据;
- 当写入数据长度超出尾部空间时,支持折行写入;
- 调用方需确保 cb_buffer 已正确初始化。
**********************************************************************/
ZUINT32 cb_write(circleBuffer * cb_buffer,ZUINT8 * inbuf,ZUINT32 wrireLen)
{
int canWriteLen=0;
//<<指针检查
if(cb_buffer==NULL ||inbuf==NULL ||wrireLen==0 )
{
return 0;
}
//检查环形buffer是否是满的
if(cb_buffer->size==cb_buffer->buflen)
{
return 0;
}
//检查剩余的空间是否够写入
//<<rdoffser:读指针 wroffset:写指针 读指针 - 写指针:有多少空间可以写
canWriteLen=(cb_buffer->rdoffser)-(cb_buffer->wroffset);
if(canWriteLen<=0) canWriteLen+=cb_buffer->buflen;
//<<没有足够的空间则不写入
if(wrireLen>canWriteLen) return -1;
//<<开始写
if(wrireLen<cb_buffer->buflen-cb_buffer->wroffset)
{
//不用折行,直接写入
memcpy(&cb_buffer->buf[cb_buffer->wroffset],inbuf,wrireLen);
}else
{
//一行写完以后,需要重新折过来,从开头在写入 wrireLen-(cb_buffer->buflen-cb_buffer->wroffset)
memcpy(&cb_buffer->buf[cb_buffer->wroffset],inbuf,cb_buffer->buflen-cb_buffer->wroffset);
memcpy(cb_buffer->buf,&inbuf[cb_buffer->buflen-cb_buffer->wroffset],wrireLen-(cb_buffer->buflen-cb_buffer->wroffset));
}
//更新写指针
cb_buffer->wroffset=(wrireLen+cb_buffer->wroffset)%cb_buffer->buflen;
//更新size
cb_buffer->size+=wrireLen;
//<<返回写的数据量
return wrireLen;
}
/***********************************************************************
*@函数名称: cb_read
*@功能描述: 从环形缓冲区中读取指定长度的数据;
*@参数:
cb_buffer[in]: 指向目标环形缓冲区的指针;
outbuf[out]: 用于存储读取数据的缓冲区;
readLen[in]: 请求读取的数据长度;
*@返回:
ZUINT32: 实际读取到的数据字节数;
*@备注:
- 该函数会检查实际可读取的字节数,确保不会读取超出有效数据的范围;
- 如果请求读取的长度大于缓冲区中的有效数据量,函数会调整为可读取的最大字节数;
- 支持跨越缓冲区尾部的折行读取,保证数据的完整性;
- 调用方需确保 cb_buffer 已正确初始化,outbuf 有足够的空间存储读取的数据。
**********************************************************************/
ZUINT32 cb_read(circleBuffer * cb_buffer,ZUINT8 * outbuf,ZUINT32 readLen)
{
int canReadLen=0;
//<<检查
if(outbuf==NULL) return 0;
//<<检查
if(cb_buffer->size==0) return 0;
//<<计算出可以读取的字节数
if (cb_buffer->size == cb_buffer->buflen)
{
canReadLen = cb_buffer->size;
}
else
{
canReadLen=cb_buffer->wroffset-cb_buffer->rdoffser;
if(canReadLen<0) canReadLen+=cb_buffer->buflen;
}
//<<想读取到的>实际有的
if(canReadLen<readLen)
{
readLen=canReadLen;
}
//<<读取数据
if(readLen<cb_buffer->buflen-cb_buffer->rdoffser)
{
//<<说明不用不用折回来读
memcpy(outbuf,&cb_buffer->buf[cb_buffer->rdoffser],readLen);
}else
{
//<<说明存到最大的地方没存完,需要继续折回来读,第一次读先读到数组的结尾
memcpy(outbuf,&cb_buffer->buf[cb_buffer->rdoffser],cb_buffer->buflen-cb_buffer->rdoffser);
memcpy(&outbuf[cb_buffer->buflen-cb_buffer->rdoffser],cb_buffer->buf,readLen-(cb_buffer->buflen-cb_buffer->rdoffser));
}
//<<更新下实际的数量大小
cb_buffer->size-=readLen;
//<<更新读指针
cb_buffer->rdoffser=(readLen+cb_buffer->rdoffser)%cb_buffer->buflen;
//<<返回读到的数据量
return readLen;
}
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)