c印记(八): ring buffer实现

时间:2023-01-13 17:46:49

一、写在前面的话

之所以自己要自己实现一个ring buffer,和前面的ini file解析的原因差不多,都是在深度定制*。
我从事是多媒体播放或者录影方面工作,且是在嵌入式平台或移动端上运行,内存,cpu运行主频等资源都相对受限,所以模组之间的数据交互,就不能简单重复的使用malloc/free的方式,这样会产生内存碎片,而且有的模组(比如硬件解码或编码)的输入输出的buffer都需要物理地址的,所以就更加无法使用malloc/free了,当然也不能使用memory copy,因为这对cpu,总线等资源的消耗也是不小的,比如解码 1080p的视频,输出的数据每一帧都相当大,使用memory copy,在一般的嵌入式平台就不太可能实现流 畅播放了。

另一方面,在视频解码的时候,往往每次都需要将一个完整帧的数据送入解码器,而且,这段数据的内存地址必须是连续的,一般的ring buffer,是可能出现异常的, 比如在ring buffer的尾端,还剩下8 KB的剩余空间,但是这一帧数据却有10KB,如果是一般的ring buffer,可能就是在尾端填 8KB的数据,然后再将写指针移到头部再填2KB数据,如此一来,就无法保证这一帧数据的地址是连续的了, 这里需要做的是,跳过这8KB空闲空间,将写指针移到头部,然后再尝试申请10KB的数据, 同时,当读指针读到尾端的时候,一样要跳过这8KB空闲空间,不能将其视为有效数据。 综合这些原因和其他一些因素,也就决定了必须得重新实现一个满足需求的ring buffer。

二、ring buffer实现

再ring buffer的实现中同样有基础数据类型的定义,错误号的定义等,由于和ini file解析是差不多的,因为在这里就不在重复叙述,直接上代码。

其头文件如下:

#ifndef __TINY_RING_BUFFER_H__
#define __TINY_RING_BUFFER_H__

#ifdef __cplusplus
extern "C"{
#endif


/***************************************************************************
*
* macro declaration
*
***************************************************************************/


/** compat the keyword 'inline' */
#if (_MSC_VER > 1200) && !defined (__cplusplus)
#define inline _inline /** to compat keyword 'inline' */
#endif

/***************************************************************************
*
* data structure declaration
*
***************************************************************************/


/** base data type define */

//general signed integer type
typedef int GS32; //32 bits

//general unsigned integer type
typedef unsigned char GU08; //8 bits
typedef unsigned int GU32; //32 bits

//boolean type declaration
typedef enum GBOL
{
GFALSE = 0,
GTRUE = !GFALSE,
}GBOL;

//general error type define
typedef enum general_error_e
{
G_OK = 0,

/** There were insufficient resources to perform the requested operation */
G_ErrorInsufficientResources = (GS32) 0x80001000,
/** There was an error, but the cause of the error could not be determined */
G_ErrorUndefined = (GS32) 0x80001001,
/** One or more parameters were not valid */
G_ErrorBadParameter = (GS32)0x80001004,
G_ErrorInvalidOperation = (GS32)0x8000101C, /** invalid operation */
G_ErrorNoMemory = (GS32)0x80001009,/** havn't enough free memory in buffer */

/** No target with the specified name string was found */
G_ErrorNotFound = (GS32)0x80001003,
}general_error_t;



/** ring buffer data structure define */
typedef struct tiny_ring_buffer_s
{
GS32 last_error; /** recording last operation errror code */
GU32 size; /** ring buffer total size */
GU08 *data; /** ring buffer base address */

/*
*@brief reset ring buffer to initial state
*
*@param trb [in] ring buffer API pointer
*
*@return none.
*
**/

void (*reset)(struct tiny_ring_buffer_s* trb);

/*
*@brief get how may bytes data cached in ring buffer
*
*@param trb [in] ring buffer API pointer
*
*@return the size of cached data in ring buffer.
*
**/

GU32 (*getCachedSize)(struct tiny_ring_buffer_s* trb);

/*
*@brief get free space in ring buffer
*
*@param trb [in] ring buffer API pointer
*
*@return the size of free space in ring buffer.
*
**/

GU32 (*getFreeSpace)(struct tiny_ring_buffer_s* trb);

/*
*@brief request/allocate memory
*
*@param trb [in] ring buffer API pointer
*@param size [in] requested memory size
*
*@return success: memory pointer, failed: NULL.
*
*@note when return NULL, user can get error code by
* tiny_ring_buffer_s.last_error
*
**/

void* (*alloc)(struct tiny_ring_buffer_s* trb, GU32 size);

/*
*@brief reallocate memory
*
*@param trb [in] ring buffer API pointer
*@param new_size [in] reallocated memory size
*@param old_ptr [in] old memory pointer
*@param old_size [in] the size of old memory
*
*@return success: memory pointer, failed: NULL.
*
*@note when return NULL, user can get error code by
* tiny_ring_buffer_s.last_error
*
**/


/** 如果需要使用realloc改变memory 大小的话,就只能是对当前最新分配的 buffer,因为 此ring buffer的memory分配和释放是遵循 FIFO机制的,不能随意是使用realloc改变非当前最新alloc的memory */
void* (*realloc)(struct tiny_ring_buffer_s* trb, GU32 new_size, void* old_ptr, GU32 old_size);

/*
*@brief free one segment memory
*
*@param trb [in] ring buffer API pointer
*@param ptr [in] memory pointer
*@param size [in] the size of memory
*
*@return none.
*
**/

void (*free)(struct tiny_ring_buffer_s* trb, void* ptr, GU32 size);

/*
*@brief print the information of read ptr, write ptr and so on.
*
*@param trb [in] ring buffer API pointer
*
*@return none.
*
**/

void (*dump)(struct tiny_ring_buffer_s* trb);

/*
*@brief destroy ring buffer instance.
*
*@param trb [in] ring buffer API pointer
*
*@return none.
*
**/

void (*destroy)(struct tiny_ring_buffer_s* trb);
}tiny_ring_buffer_t;

/***************************************************************************
*
* API declaration
*
***************************************************************************/


/*
*@brief create ring buffer instance
*
*@param trb [out] ring buffer API instance pointer
*@param size [in] the request ring buffer size
*@param data [in] extern supply the memory pointer, if put as NULL, the
* tiny ring buffer will auto allocate memory with input
* 'size'
*
*@return none.
*
**/

GS32 tinyRingBufferCreate(tiny_ring_buffer_t** trb, GU32 size, void* data);


#ifdef __cplusplus
}
#endif


#endif //end of __TINY_RING_BUFFER_H__

具体的实现文件如下:

#define _CRT_SECURE_NO_WARNINGS

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include "tiny_ring_buffer.h"


/***************************************************************************
*
* macro define
*
***************************************************************************/

#define LOGI printf
#define LOGE printf
/***************************************************************************
*
* data structure define
*
***************************************************************************/


typedef struct my_ring_buffer_s
{
tiny_ring_buffer_t common;

GBOL is_owns_data; /** GTRUE: ring buffer memory allocated by itself */
GU32 free_size; /** recording free memory size in ring buffer */
GU32 cached_size; /** recording cached data size in ring buffer */

GU08 *end_ptr; /** the end pointer of ring buffer */
GU08 *write_ptr;
GU08 *read_ptr;
GU08 *trail_ptr; /** the end pointer of cached data */
}my_ring_buffer_t;

/***************************************************************************
*
* inner API define
*
***************************************************************************/


//检测ring buffer是否有足够的剩余空间满足请求的memory的大小
static GS32 TRBCheckRequestSize(my_ring_buffer_t* mrb, GU32 size, GBOL* is_need_turnback_ptr)
{
GS32 ret = G_ErrorNoMemory;
*is_need_turnback_ptr = GFALSE;

if (size <= mrb->free_size)
{
if (mrb->read_ptr <= mrb->write_ptr)
{
GU32 trail_size = (mrb->end_ptr - mrb->write_ptr);

if (trail_size >= size)
{
ret = G_OK;
}
else
{
/** not enough memory for request in tailer of buffer,
* so need turnback and check memory size from the header of buffer
*/

if ((mrb->free_size - trail_size) >= size)
{
*is_need_turnback_ptr = GTRUE;
ret = G_OK;
}
}

}
else
{
/** in this case free size == mrb->read_ptr - mrb->write_ptr ,
* so needn't compute tail_size,
*/

ret = G_OK;
}
}

return ret;
}

/** 翻转写指针 到ring buffer头部 */
static inline void TRBTurnbackWritePtr(my_ring_buffer_t* mrb)
{
mrb->free_size -= (mrb->end_ptr - mrb->write_ptr);
mrb->trail_ptr = mrb->write_ptr;
mrb->write_ptr = mrb->common.data;
}

/** 更新写指针 */
static inline void TRBUpdateWritePtr(my_ring_buffer_t* mrb, GS32 size)
{
mrb->write_ptr += size;
mrb->free_size -= size;
mrb->cached_size += size;
}

/** 翻转读指针 到ring buffer 头部 */
static inline void TRBTurnbackReadPtr(my_ring_buffer_t* mrb)
{
mrb->free_size += (mrb->end_ptr - mrb->read_ptr);
mrb->read_ptr = mrb->common.data;
mrb->trail_ptr = NULL;
}

/** 更新读指针 */
static inline void TRBUpdateReadPtr(my_ring_buffer_t* mrb, GS32 size)
{
mrb->read_ptr += size;
mrb->free_size += size;
mrb->cached_size -= size;
}

/** 获取从读指针开始的连续有效数据长度 */
static inline GU32 TRBGetValidDataSize(my_ring_buffer_t* mrb)
{
return (mrb->trail_ptr) ? (mrb->trail_ptr - mrb->read_ptr) : (mrb->write_ptr - mrb->read_ptr);
}

///////////////////////////////////////////////////////////////////////////
static void TRBReset(struct tiny_ring_buffer_s* trb)
{
my_ring_buffer_t* mrb = (my_ring_buffer_t*)trb;

mrb->read_ptr = trb->data;
mrb->write_ptr = trb->data;
mrb->end_ptr = trb->data + trb->size;
mrb->trail_ptr = NULL;

mrb->free_size = trb->size;
mrb->cached_size = 0;
}

static GU32 TRBGetCachedSize(struct tiny_ring_buffer_s* trb)
{
return ((my_ring_buffer_t*)trb)->cached_size;
}

static GU32 TRBGetFreeSpace(struct tiny_ring_buffer_s* trb)
{
return ((my_ring_buffer_t*)trb)->free_size;
}

static void* TRBAlloc(struct tiny_ring_buffer_s* trb, GU32 size)
{
my_ring_buffer_t* mrb = (my_ring_buffer_t*)trb;
GU08 *req_buf = NULL;
GS32 ret = G_ErrorBadParameter;

if (size > 0)
{
if (mrb->free_size > 0)/** have free size */
{
GBOL is_need_turnback = GFALSE;

/** 检测请求size */
ret = TRBCheckRequestSize(mrb, size, &is_need_turnback);

if (G_OK == ret)
{
if (is_need_turnback)
{/** 如果需要翻转写指针,就调用相关函数 */
TRBTurnbackWritePtr(mrb);
}

/** check condition ok, so allocate memory */
req_buf = mrb->write_ptr;
/** 获取到需要的memory之后,更新写指针 */
/** update write ptr */
TRBUpdateWritePtr(mrb, size);
}
}
else
{
ret = G_ErrorNoMemory;
}
}

trb->last_error = ret; /** recording error code */

return req_buf;
}

static void* TRBRealloc(struct tiny_ring_buffer_s* trb, GU32 new_size, void* old_ptr, GU32 old_size)
{
my_ring_buffer_t* mrb = (my_ring_buffer_t*)trb;
GS32 ret = G_ErrorBadParameter;
void* new_ptr = NULL;

if (new_size > 0)
{
if (old_ptr && (old_size > 0))
{
if (new_size <= old_size)
{
GS32 revert_Size = old_size - new_size;

TRBUpdateWritePtr(mrb, -revert_Size);
new_ptr = old_ptr;
ret = G_OK;
}
else
{
GBOL is_need_turnback_write_ptr = GFALSE;
ret = TRBCheckRequestSize(mrb, new_size, &is_need_turnback_write_ptr);

if (G_OK == ret)
{
if (is_need_turnback_write_ptr == GTRUE)
{
//turnback wirte pointer
TRBTurnbackWritePtr(mrb);
//copy old data
memcpy(mrb->write_ptr, old_ptr, old_size);
}

new_ptr = mrb->write_ptr;

/** update write ptr */
TRBUpdateWritePtr(mrb, new_size);
}
}
}
else
{
new_ptr = TRBAlloc(trb, new_size);
ret = trb->last_error;
}
}

trb->last_error = ret;

return new_ptr;
}

static void TRBFree(struct tiny_ring_buffer_s* trb, void* ptr, GU32 size)
{
my_ring_buffer_t* mrb = (my_ring_buffer_t*)trb;
GS32 ret = G_ErrorBadParameter;
GU08* buf = (GU08*)ptr;

if (buf && (size > 0))
{
if ((trb->data <= buf) && (buf < mrb->end_ptr))
{
if (mrb->read_ptr == mrb->trail_ptr)
{
TRBTurnbackReadPtr(mrb);
}

if (ptr == mrb->read_ptr)
{
GU32 valid_data_size = TRBGetValidDataSize(mrb); /** valid trail data size */

if (size <= valid_data_size)
{
TRBUpdateReadPtr(mrb, size);
ret = G_OK;
}
else
{
LOGE("request free size(%d) > valid data size(%d)\n", size, valid_data_size);
}
}
else
{
LOGE("free memory need follow allcate order(like FIFO)\n");
}
}
else
{
LOGE("request free buffer(%p, size:%d) out of range(%p - %p)\n", ptr, size, trb->data, mrb->end_ptr);
}
}

trb->last_error = ret;
}

static void TRBDump(struct tiny_ring_buffer_s* trb)
{
my_ring_buffer_t* mrb = (my_ring_buffer_t*)trb;
LOGI("mHead(%p), mEnd(%p), write_ptr(%p) ,read_ptr(%p) \n",
trb->data, mrb->end_ptr, mrb->write_ptr, mrb->read_ptr);
}

static void TRBDestroy(struct tiny_ring_buffer_s* trb)
{
my_ring_buffer_t* mrb = (my_ring_buffer_t*)trb;

if ((mrb->is_owns_data == GTRUE) && (trb->data))
{
free(trb->data);
}

free(mrb);
}

/***************************************************************************
*
* API define
*
***************************************************************************/


GS32 tinyRingBufferCreate(tiny_ring_buffer_t** trb, GU32 size, void* data)
{
GS32 ret = G_ErrorBadParameter;

if (trb && (size > 0))
{
my_ring_buffer_t* mrb = (my_ring_buffer_t*)malloc(sizeof(my_ring_buffer_t));

if (mrb)
{
GU08* base_ptr = data;
memset(mrb, 0, sizeof(*mrb));

if (data == NULL)
{
base_ptr = (GU08*)malloc(size);
mrb->is_owns_data = GTRUE;
}

if (base_ptr)
{
tiny_ring_buffer_t* temp = (tiny_ring_buffer_t*)mrb;

temp->data = base_ptr;
temp->size = size;
temp->last_error = G_OK;

temp->alloc = TRBAlloc;
temp->destroy = TRBDestroy;
temp->dump = TRBDump;
temp->free = TRBFree;
temp->getCachedSize = TRBGetCachedSize;
temp->getFreeSpace = TRBGetFreeSpace;
temp->realloc = TRBRealloc;
temp->reset = TRBReset;

TRBReset(temp);

*trb = temp;

ret = G_OK;
}
else
{
free(mrb);
}
}
else
{
LOGE("allocate ring buffer intance failed\n");
ret = G_ErrorInsufficientResources;
}
}

return ret;
}

三、使用例子

#include <stdio.h>
#include "tiny_ring_buffer.h"

///////////////////////////////////////////////////////////////////////////////
#define RING_BUF_SIZE (16 * 1024)
#define REQ_BUF_SIZE (1024)
#define ALIGN_SIZE (1024)

#define LOGD printf
#define LOGE printf

////////////////////////////////////////////////////////////////////////////////
static GU08 g_buf[RING_BUF_SIZE] = {0};


///////////////////////////////////////////////////////////////////////////////

int main(int argc, char *argv[])
{
GU32 i = 0;
GS32 ret = G_OK;
GU32 do_flag = 0;
GU32 test_num = 0;
GU08 *temp_buf = NULL;
GU08 *last_buf = NULL;
tiny_ring_buffer_t* trb = NULL;
//create a ring buffer instance.

ret = tinyRingBufferCreate(&trb, RING_BUF_SIZE, NULL);


LOGD("strat test tiny ring buffer...\n");

LOGD("test(%d), supply memory by ring buffer self(malloc)...\n", test_num);
do
{
do_flag = 0;

if (!trb)
{
LOGE("Error: create a ring buffer instance failed... \n");
ret = G_ErrorInsufficientResources;
break;
}

LOGD("<<======================================>> \n");
ret = trb->size;
LOGD("create size(%d), reallysize(%d)\n", RING_BUF_SIZE, ret);
if (ret != RING_BUF_SIZE)
{
LOGE("ring buffer size invalid(%d), request(%d)\n", trb->size, RING_BUF_SIZE);
ret = G_ErrorUndefined;
break;
}
else
{
ret = G_OK;
}

for (i = 0; i < RING_BUF_SIZE / REQ_BUF_SIZE; i++)
{
temp_buf = trb->alloc(trb, REQ_BUF_SIZE);
LOGD("alloc buffer(%p), size(%d)\n", temp_buf, REQ_BUF_SIZE);
if (temp_buf == NULL)
{
LOGE("alloc buffer failed(0x%08x)\n", trb->last_error);
break;
}

if ((i % 2))
{
trb->free(trb, last_buf, REQ_BUF_SIZE * 2);
LOGD("free buffer(%p), size(%d)\n", last_buf, REQ_BUF_SIZE * 2);
if (trb->last_error != G_OK)
{
LOGE("free buffer failed(0x%08x)\n", trb->last_error);
break;
}

last_buf = NULL;
}
else
{
last_buf = temp_buf;
}
}

//todo test

LOGD("<<======================================>> \n");
test_num++;

if (test_num < 2)
{
trb->destroy(trb);
trb = NULL;

//test external provide buffer
ret = tinyRingBufferCreate(& trb, RING_BUF_SIZE, g_buf);
LOGD("test(%d), supply memory by external buffer...\n", test_num);
do_flag = 1;
continue;
}

} while (do_flag);

if (trb)
{
trb->destroy(trb);
}

LOGD("end of test tiny ring buffer...\n");

return 0;
}