###### 高效的两段式循环缓冲区──BipBuffer

Simon Cooke，美国 （原作者）

循环缓冲区是一个非常常用的数据存储结构，已经被广泛地用于连续、流数据的存储和通信应用中。对于循环缓冲区，传统的操作方法是开辟一块连续的存储区，不断地写入数据，当写入到存储区的末尾的时候，再从存储区的首部再开始写入数据，由此不断地重复下去构成了循环缓冲区。偶曾经写过很多循环缓冲区，也看过很多人编写的循环缓冲区，但是拜读Simon Cooke先生的文章────“两段式”循环缓冲区（原文名称是：The Bip Buffer - The Circular Buffer with a Twist）确实觉得与众不同，于是就有了把它介绍给国内开发者的意愿。这里的twist的意思是“缠绕、绞合”，在这里有紧密联系的意味，作者的本意是希望通过twist这个词能够体现出这个循环缓冲区的特点，但是如果直译出来，会让很多人感到费解。所以在此，根据偶个人的理解将这个标题翻译成“两段式”循环缓冲区。接下来偶会把英文原文跟偶的理解写出来，感兴趣的朋友可以对照着看，如果翻译有误的地方还请个位高手不吝斧正！

────译者

1、Introduction 简介

Instead of keeping one head and tail pointer to the data in the buffer, it maintains two revolving regions, allowing for fast data access without having to worry about wrapping at the end of the buffer.

Buffer allocations are always maintained as contiguous blocks, allowing the buffer to be used in a highly efficient manner with API calls, and also reducing the amount of copying which needs to be performed to put data into the buffer. Finally, a two-phase allocation system allows the user to pessimistically reserve an area of buffer space, and then trim back the buffer to commit to only the space which was used..

Let's cover a little history first. If you don't already know why a circular buffer can be implemented really efficiently in hardware, or why that makes them the buffer of choice in most electronics, here's why.

Bip-Buffer使用起来有些类似循环缓冲区，但是在结构上略有不同。Bip-Buffer内部采用了两个循环存储区（而不是靠维持头指针和尾指针）来实现数据的高速存取，而且可以让Bip-Buffer的使用者完全不必担心写入数据到达缓冲区末尾，导致重新从缓冲区的首部开始写入的问题。Bip-Buffer维护的存储区是连续的，因此，Bip-Buffer可以通过API调用非常高效地使用存储区，在整个使用过程中可以最大限度避免使用诸如memcpy(),memmove()之类的内存拷贝操作（通常对于循环缓冲区来说，频繁地调用内存操作函数会成为效率瓶颈）。最后，Bip-Buffer两段式的内存分配系统允许用户申请一块较大的内存，而通过Commit操作来确认真正需要的内存大小，然后把没有用完的内存回收。

2.Back in Days of Old... “石器”时代

Once upon a time, computers were much simpler. They didn't have 64 bit data buses. Heck, they didn't even have real 16 bit registers - although you could occasionally convince a pair of them to sub in for that purpose. These were simpler times, where Real Men programmed in assembly language, and laughed at anyone who didn't know how to use the carry flag for all kinds of nefarious purposes.

With simpler times came elegant hacks to eke the most power out of every instruction cycle available. Take, for example, a simple terminal communications program. Newer RS232 serial controllers had things like automatic handling of RTS and CTS signal lines to control the flow of data - but this came at a cost. Namely, the connection would be stopping and starting all the time, instead of streaming along. So in between the controller card and the system, would often be found a FIFO. This simple circular buffer was often no more than a couple of bytes long, but it meant that the system could run smoothly along without polling to see if data had arrived, or being hammered by constant interrupts from the serial controller.

Most FIFOs started out on-chip, but people also added their own in their code - the idea being that if you had some really gnarly dancing that you had to do on the incoming data, you may as well batch it all up into one lump and do it infrequently... giving spare time to the system to do other things. Like scroll the console, or decode GIFs.

As I said, a FIFO is a very simple circular buffer. Most are implemented very simply as well; they're typically 2n bytes in size, which allows the pointers to simply overflow to get back around to the other end of the buffer. The FIFO logic can tell if the FIFO is empty because the head and tail values are the same, and it's full if the head is one greater than the tail.

Implementing these in software was easy on the old 8 bit systems. Take a 16 bit register pair. Decide on a location in memory (a multiple of 256) to store the FIFO data in. Then, after setting the register to the start of the buffer, don't touch the high register - just increment the low register. This gives you a 256-byte long buffer which you can walk through in one (in the case of the Zilog Z80, 4 cycles - the smallest execution unit available on that system) instruction. You can never go out of the bounds of your buffer, because the low register acts as an index with a value from 0 to 255. When you hit what would have been index 256, the register overflows and clocks back over to zero.

3. The Modern Day “帝国”时代

Unfortunately, there is no solution quite as elegant available to Windows programmers today as that simple old 8-bit solution. Sure, you can dive down into assembly language (provided you can work out how the compiler maps registers to values... something I've never seen a good enough explanation of to get my head around), but most people don't have time for assembly language any more. And besides, we're dealing with 32 bit registers now - incrementing just one low-order byte from inside that register isn't really all that kosher any more. It can lead to cache flushing, pipeline stalling, printer fires, rains of frog, etc.

If you can't just clock the low-order register to walk through the buffer, you have to start worrying about things like checking to see how much buffer you have filled before the end, making sure that you remember to copy the rest of the data from the start of the buffer, and all kinds of other bookkeeping headaches.

My first attempt at implementing something like this relied on the vague hope that the virtual memory system could be tricked into setting things up in such a way that you could set up a mirror of a section of memory right next to the original. The idea being that you could still use the rotating allocation of data; a copy operation could go at full speed without any checking to see if you'd walked off the end of the buffer - because as far as your process's address space is concerned, the end of your buffer is also the beginning of your buffer.

Now, this mirroring technique may actually work. Due to some restrictions, I decided not to implement it myself (yet - I'm sure I'll find a use for it some day). The idea behind it is that first one reserves two areas of virtual memory, side by side. One then maps the same temporary file into both virtual memory sections. Voila! Instant mirroring, and a nice large buffered expanse one can copy data from willy-nilly.

Unfortunately, while it should (again, I've not tried it) indeed work, there is another problem - namely, that files can only be mapped on 64kb boundaries (possibly larger on larger memory systems). This means that your buffer has to be a minimum of 64kb in size, and will take up 128kb of your virtual address space. Depending on your application, this may be a valid technique. However, I don't see writing a server application with 1000's of sockets being a valid prospect here.
So what to do? If mirroring won't work, how close can we get to using a circular buffer in our code? Heck, even if we can get close, why would we want to?

4 The Advantages of the Circular Buffer 使用循环缓冲区的优点
There are a number of key advantages to using a circular buffer for the temporary storage of data.When one puts data into a block of memory, one also has to take it out again to make use of it. (Or one can use it in place). It is useful to be able to make use of the data in the buffer while more data is being appended to the buffer. However, as one frees up space at the head of the buffer, that space is no longer usable, unless one copies all of the data in the buffer which has not yet been used to the beginning of the buffer. This frees up space at the end of the buffer, allowing more data to be appended.

There are a couple of ways around this; one can simply copy the data (which is a reasonably expensive proposition), or one can extend the buffer to allow more data to be appended (a massively expensive process).With a circular buffer, the free space in the buffer is always available to have data appended into it; the data is copied, the pointer adjusted, and that's that. No copying, no reallocation, no worries. The buffer is allocated once, and then remains useful for its entire life.

5 A Fly In The Ointment 循环缓冲区美中不足的地方
One could simply implement a circular buffer by allocating a chunk of memory, and maintaining pointers. When one walked off the end of the buffer, the pointer would be adjusted - and this operation would be reflected in every operation that is performed, whether copying data into the buffer or removing it. Length calculations are slightly more complicated than normal, but not overly so - simple inline functions take care of that problem with ease, sweeping it under the rug.
Unfortunately, most API calls don't believe in circular buffers. You have to pass them a single contiguous block of memory which they can access, and there is no way for you to modify their write behavior to adjust pointers when they cross the end of the buffer. So what to do? Well, this is where the Bip-Buffer comes in.

6 Enter The Bip-Buffer 进一步了解Bip-Buffer
If one cannot pass a circular buffer into an API function, then one needs an alternative that will work - preferably with many of the same advantages as the circular buffer. It is possible to build a circular buffer which is split into two regions - or which is bi-partite (and that's how you get the Bip in a Bip-Buffer). Each of the two regions move through the buffer, starting at the left and ending up at the right hand side. When one runs out of space for appending data, if there is only one region, a new one is created at the beginning (if possible). The diagram below shows how it works in more detail.

The buffer starts out empty, with no regions present (figure 1). (eg. immediately after calling AllocatedBuffer)

Then, when data is first put into the buffer, a single region (the 'A' region) is created (figure 2). (Say, by calling Reserve followed by Commit)

Data is added to the region, extending it to the right (figure 3).

For the first time now, we remove data from the buffer (figure 4). (see the DecommitBlock call described below)

This continues until the region reaches the end of the buffer (figure 4). Once more free space is available to the left of region A than to the right of it, a second region (comically named "region B") is created in that space. The choice to create a new region when more space is available on the left is made to maximize the potential free space available for use in the buffer. The upshot of all this leaves us with something which looks rather like figure 5.

If we now use up more of the buffer space, we end up with figure 6, with new space only being allocated from the end of region B. If we eventually allocate enough data to use up all of the free space between regions A and B (figure 7), we no longer have any usable space in the buffer, and no more reservations can be performed until we read some data out of it.

If we then read more data out of the buffer (say the entire remaining contents of region A), we exhaust it entirely. At the point, as region A is completely empty, we no longer need to track two separate regions, and all of region B's internal data is copied over region A's internal data, and region B is entirely cleared. (figure 8)

If we read a little more data out of the buffer, we now end up with something a lot like figure 4, and the cycle continues.

7 Characteristics of the Bip-Buffer BipBuffer的特点
The upshot of all of this is that on average, the buffer always has the maximal amount of free space available to be used, while not requiring any data copying or reallocation to free up space at the end of the buffer.

The biggest difference from an implementation standpoint between a regular circular buffer and the Bip Buffer is the fact that it only returns contiguous blocks. With a circular buffer, you need to worry about wrapping at the end of the buffer area - which is why for example if you look at Larry Antram's Fast Ring Buffer implementation, you'll see that you pass data into the buffer as a pointer and a length, the data from which is then copied byte by byte into the buffer to take into account the wrapping at the edges.

BipBuffer与常规的循环缓冲区相比较，最大的区别在于它可以返回连续的存储区。使用常规的循环缓冲区，需要考虑如何对缓冲区的末尾进行封装。这也是当看到Larry Antram's Fast Ring Buffer文章中对于循环缓冲区实现的时候，会发现需要传入数据缓冲区的指针以及数据的长度，然后数据会一个字节一个字节地拷贝到循环缓冲区中去（当然他的这种实现是需要考虑缓冲区的边界问题的。）

Another possibility which was brought up in the bulletin board (and the person who brought it up shall remain nameless, if just because they... erm... are nameless) was that of just splitting the calls across wraps. Well, this is one way of working around the wrapping problem, but it has the unfortunate side-effect that as your buffer fills, the amount of free space which you pass out to any calls always decreases to 1 byte at the minimum - even if you've got another 128kb of free space at the beginning of your buffer, at the end of it you're still going to have to deal with ever shrinking block sizes. The Bip-Buffer neatly sidesteps this issue by just leaving that space alone if the amount you request is larger than the remaining space at the end of the buffer. When writing networking code, this is very useful; you always want to try to receive as much data as possible, but you never can guarantee how much you're going to get. (For most optimal results, I'd recommend allocating a buffer which is some multiple of your MTU size).

Yes, you are going to lose some of what would have been free space at the end of the buffer. It's a small price to pay for playing nicely with the API.

Use of this buffer does require that one checks twice to see if the buffer has been emptied; as one has to deal with the possibility that there are two regions currently in use. However, the flexibility and performance gains outweigh this minor inconvenience.

class BipBuffer
{
private:
BYTE* pBuffer;
int ixa, sza, ixb, szb, buflen, ixResrv, szResrv;

public:
BipBuffer();

The constructor initializes the internal variables for tracking regions, and memory pointers to null; it does not allocate any memory for the buffer, in case one needs to use the class in an environment where exception handling cannot be used.

~BipBuffer();

The destructor simply frees any memory which has been allocated to the buffer.

bool AllocateBuffer(int buffersize = 4096);

AllocateBuffer allocates a buffer from virtual memory. The size of the buffer is rounded up to the nearest full page size. The function returns true if successful, or false if the buffer cannot be allocated.

void FreeBuffer();

FreeBuffer frees any memory allocated to the buffer by the call to AllocateBuffer, and releases any regions allocated within the Bip-Buffer.

bool IsInitialized() const;

IsInitialized returns true if the buffer has had memory allocated to it (by calling AllocateBuffer), or false if there is no memory allocated to the buffer.

int GetBufferSize() const;

GetBufferSize returns the total size (in bytes) of the buffer. This may be greater than the value passed into AllocateBuffer, if that value was not a multiple of the system's page size.

void Clear();

Clear ... well... clears the buffer. It does not free any memory allocated to the buffer; it merely resets the region pointers back to null, making the full buffer usable for new data again.

BYTE* Reserve(int size, OUT int& reserved);

Now to the nitty-gritty. Allocating data in the Bip-Buffer is a two-phase operation. First an area is reserved by calling the Reserve function; then, that area is Committed by calling the Commit function. This allows one to, say, reserve memory for an IO call, and when that IO call fails, pretend it never happened. Or alternatively, in a call to an overlapped WSARecv() function, it allows one to advertise how much memory is available to the network stack to use for incoming data, and then adjust the amount of space used based on how much data was actually read in (which may be less than the requested amount).

To use Reserve, pass in the size of block requested. The function will return the size of the largest free block available which is less than or equal to size in length in the reserved parameter you passed in. It will also return a BYTE* pointer to the area of the buffer which you have reserved.

In the case where the buffer has no space available, Reserve will return a NULL pointer, and reserved will be set to zero.

Note: you cannot nest calls to Reserve and Commit; after calling Reserve you must call Commit before calling Reserve again.

void Commit(int size);

Here's the other half of the allocation. Commit takes a size parameter, which is the number of bytes (starting at the BYTE* you were passed back from Reserve) which you have actually used and want to keep in the buffer. If you pass in zero for this size, the reservation will be completely released, as if you had never reserved any space at all. Alternatively, in a debug build, if one passes in a value greater than the original reservation, an assert will fire. (In a release build, the original reservation size will be used, and no one will be any the wiser). Committing data to the buffer makes it available for routines which take data back out of the buffer.

The diagram above shows how Reserve and Commit work. When you call Reserve, it will return a pointer to the beginning of the gray area above (fig. 1). Say you then only use as much of that buffer as the blue section (fig 2). It'd be a shame to leave this area allocated and going to waste, so you can call Commit with only as much data as you used, which gives you fig. 3 - namely, the committed space extends to fill just the part you needed, leaving the rest free.

int GetReservationSize() const;

If at any time you need to find out if you have a pending reservation, or need to find out that reservation's size, you can call GetReservationSize to find the amount reserved. No reservation? You'll get a zero back.

BYTE* GetContiguousBlock(OUT int& size);

Well, after all this work to put stuff into the buffer, we'd better have a way of getting it out again.

First of all, what if you need to work out how much data (total) is available to be read from the buffer?

int GetCommittedSize() const;

One method is to call GetCommittedSize, which will return the total length of data in the buffer - that's the total size of both regions combined. I would not recommend relying on this number, because it's very easy to forget that you have two regions in the Bip-Buffer if you do. And that would be a bad thing (as several weeks of painful debugging experience has proved to me). As an alternative, you can call:

BYTE* GetContiguousBlock(OUT int& size);

... which will return a BYTE* pointer to the first (as in FIFO, not left-most) contiguous region of committed data in the buffer. The size parameter is also updated with the length of the block. If no data is available, the function returns NULL (and the size parameter is set to zero).

In order to fully empty the buffer, you may wish to loop around, calling GetContiguousBlock until it returns NULL. If you're feeling miserly, you can call it only twice. However, I'd recommend the former; it means you can forget that there's two regions, and just remember that there's more than one.

void DecommitBlock(int size);

So what do you do after you've consumed data from the buffer? Well, in keeping with the spirit of the aforementioned Reserve and Commit calls, you then call DecommitBlock to release data from it. Data is released in FIFO order, from the first contiguous block only - so if you're going to call DecommitBlock, you should do it pretty shortly after calling GetContiguousBlock. If you pass in a size of greater than the length of the contiguous block, then the entire block is released - but none of the other block (if present) is released at all. This is a deliberate design choice to remind you that there is more than one block and you should act accordingly. (If you really need to be able to discard data from blocks you've not read yet, it's not too difficult to copy the DecommitBlock function and modify it so that it operates on both blocks; just unwrap the if statement, and adjust the size parameter after the first clause. Implementation of this is left as the dreaded youknowwhat).

And that's the Bip-Buffer implementation done. A short example of how to use it is provided below.

#include "BipBuffer.h"

BipBuffer buffer;
SOCKET s;

bool StartUp
{
// Allocate a buffer 8192 bytes in length
if (!buffer.AllocateBuffer(8192)) return false;

s = socket(...

... do something else ...
}

void Foo()
{
_ASSERTE(buffer.IsValid());

// Reserve as much space as possible in the buffer:

int space;
BYTE* pData = buffer.Reserve(GetBufferSize(), space);

// We now have *space* amount of room to play with.

if (pData == NULL) return;

// Obviously we've not emptied the buffer recently

// because there isn't any room in it if we return.

// Let's use the buffer!

int recvcount = recv(s, (char*)pData, space, 0);

if (recvcount == SOCKET_ERROR) return;
// heh... that's some kind of error handling...

// We now have data in the buffer (or, if the

// connection was gracefully closed, we don't have any)

buffer.Commit(recvcount);

if (recvcount == 0) read_EOF = true;

}

void Bar()
{
_ASSERTE(buffer.IsValid());

// Let's empty the buffer.

int allocated;
BYTE* pData;

while (pData = buffer.GetContiguousBlock(allocated)
!= NULL)
{
// Let's do something with the data.

fwrite(pData, allocated, 1, outputfile);

// (again, lousy error handling)

buffer.DecommitBlock(allocated);
}
}

代码：
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include "bipbuffer.h"

struct __bipbuffer_info{
unsigned long size_b;		//b空间大小
unsigned long size_a;		//a空间大小
unsigned long offer_a;		//b空间偏移
unsigned long offer_b;		//a空间偏移
unsigned long buffer_len;	//缓冲区大小
unsigned long offer_reserve;//储存空闲空间偏移
unsigned long size_reserve;	//储存空闲空间大小
};
struct __bipbuffer_info *p_bipbuffer_info = NULL;

/*
*函数名称：init_bipbuffer_struct
*函数功能：初始化结构
*参数说明：size 传值
*返 回 值：void
*/
static void init_bipbuffer_struct(unsigned long size)
{
//初始化结构
p_bipbuffer_info->size_a = 0;
p_bipbuffer_info->size_b = 0;
p_bipbuffer_info->offer_a = 0;
p_bipbuffer_info->offer_b = 0;
p_bipbuffer_info->offer_reserve = 0;
p_bipbuffer_info->size_reserve = 0;
p_bipbuffer_info->buffer_len = size;
}

/*
*函数名称：init_bipbuffer
*函数功能：分配高效两段式循环缓冲区
*参数说明：size 要分配缓冲区的大小，一般是以页为单位
*返 回 值：成功返回0，失败返回负数
*/
int init_bipbuffer(unsigned long size)
{
p_bipbuffer_info = (struct __bipbuffer_info *)malloc(sizeof(struct __bipbuffer_info));
if (p_bipbuffer_info == NULL)
return -1;

//分配缓冲区大小
free(p_bipbuffer_info);
p_bipbuffer_info = NULL;
return -2;
}

init_bipbuffer_struct(size);

return 0;
}

/*
*函数名称：destroy_bipbuffer
*函数功能：释放缓冲区
*参数说明：void
*返 回 值：成功返回0
*/
int destroy_bipbuffer(void)
{
}

if (p_bipbuffer_info){
free(p_bipbuffer_info);
p_bipbuffer_info = NULL;
}

return 0;
}

/*
*函数名称：get_space_after_a
*函数功能：获得内存块A后空闲缓冲区的大小
*参数说明：void
*返 回 值：空闲缓冲区的大小
*/
static unsigned long get_space_after_a(void)
{
return p_bipbuffer_info->buffer_len -
p_bipbuffer_info->offer_a - p_bipbuffer_info->size_a;
}

/*
*函数名称：get_b_free_space
*函数功能：获得内存块B空闲缓冲区的大小
*参数说明：void
*返 回 值：空闲缓冲区的大小
*/
static unsigned long get_b_free_space(void)
{
return p_bipbuffer_info->offer_a -
p_bipbuffer_info->offer_b - p_bipbuffer_info->size_b;
}

/*
*函数功能：获得空闲缓冲区的地址
*参数说明：want_size：需要的大小，actually_size实际能给出的大小
*返 回 值：有空闲空间返回地址，没有则返回NULL
*/
static char *get_reserve_addr(unsigned long want_size, unsigned long *actually_size)
{
unsigned long free_space_size;
/*
We always allocate on B if B exists;
this means we have two blocks and our buffer is filling.
*/
if (p_bipbuffer_info->size_b)
{
free_space_size = get_b_free_space();
if (free_space_size == 0)
return NULL;

//多余的空间大于需要的空间，说明有多余的空间
if (want_size < free_space_size)
free_space_size = want_size;

p_bipbuffer_info->offer_reserve = free_space_size;
*actually_size = free_space_size;
p_bipbuffer_info->offer_reserve = p_bipbuffer_info->offer_b + p_bipbuffer_info->size_b;
}
else
{
/* Block b does not exist, so we can check
if the space after a is bigger than the space before A,
and allocate the bigger one.
*/
/*
如果内存块B不存在，检查空闲空间是否比内存块A大，分配最大的
*/
free_space_size = get_space_after_a();
printf("\nfree space:%ld", free_space_size);
if (free_space_size >= p_bipbuffer_info->offer_a)//内存块A后有更大的空间
{
if (free_space_size == 0)
return NULL;

//如果需要分配的空间小于空闲的空间
if (want_size < free_space_size)
free_space_size = want_size;

p_bipbuffer_info->size_reserve = free_space_size;
*actually_size = free_space_size;
p_bipbuffer_info->offer_reserve = p_bipbuffer_info->offer_a + p_bipbuffer_info->size_a;
}
else//空闲空间不够
{
if (p_bipbuffer_info->offer_a == 0)
return NULL;

if (p_bipbuffer_info->offer_a < want_size)
want_size = p_bipbuffer_info->offer_a;

p_bipbuffer_info->size_reserve = want_size;
*actually_size = want_size;
p_bipbuffer_info->offer_reserve = 0;
}
}

return NULL;
}

/*
*函数名称：commit_data
*函数功能：数据写入后，修改缓冲区的指针
*参数说明：size：大小
*返 回 值：void
*/
static void commit_data(unsigned long size)
{
if (size == 0)
{
// decommit any reservation
p_bipbuffer_info->size_reserve  = 0;
p_bipbuffer_info->offer_reserve = 0;
return;
}

/* If we try to commit more space than we asked for,
clip to the size we asked for.
*/
if (size > p_bipbuffer_info->size_reserve)
size = p_bipbuffer_info->size_reserve;

// If we have no blocks being used currently, we create one in A.
if (p_bipbuffer_info->size_a == 0 && p_bipbuffer_info->size_b == 0)
{
p_bipbuffer_info->offer_a = p_bipbuffer_info->offer_reserve;
p_bipbuffer_info->size_a = size;

p_bipbuffer_info->offer_reserve = 0;
p_bipbuffer_info->size_reserve = 0;
return;
}

if (p_bipbuffer_info->offer_reserve ==
p_bipbuffer_info->size_a + p_bipbuffer_info->offer_a)
p_bipbuffer_info->size_a += size;
else
p_bipbuffer_info->size_b += size;

p_bipbuffer_info->offer_reserve = 0;
p_bipbuffer_info->size_reserve = 0;
}

/*
*函数名称：get_contiguous_block
*函数功能：获得可读的数据
*参数说明：size：大小
*返 回 值：返回可读的首地址
*/
static char *get_contiguous_block(unsigned long *size)
{
if (p_bipbuffer_info->size_a == 0)
{
*size = 0;
return NULL;
}

*size = p_bipbuffer_info->size_a;
}

/*
*函数名称：commit_block
*函数功能：数据读出后，修改缓冲区的指针
*参数说明：size：大小
*返 回 值：void
*/
static void commit_block(unsigned long size)
{
if (size >= p_bipbuffer_info->size_a)
{
p_bipbuffer_info->offer_a = p_bipbuffer_info->offer_b;
p_bipbuffer_info->size_a = p_bipbuffer_info->size_b;
p_bipbuffer_info->offer_a = 0;
p_bipbuffer_info->size_b = 0;
}
else
{
p_bipbuffer_info->size_a -= size;
p_bipbuffer_info->offer_a += size;
}
}

/*
*函数名称：get_committed_size
*函数功能：获得块A和块B的总大小
*参数说明：void
*返 回 值：大小
*/
unsigned long get_committed_size(void)
{
return p_bipbuffer_info->size_a + p_bipbuffer_info->size_b;
}

/*
*函数名称：get_reservation_size
*函数功能：获得存储的大小
*参数说明：void
*返 回 值：大小
*/
unsigned long get_reservation_size(void)
{
return p_bipbuffer_info->size_reserve;
}

/*
*函数名称：get_buffer_size
*函数功能：获得整个缓冲区的大小
*参数说明：void
*返 回 值：大小
*/
unsigned long get_buffer_size(void)
{
return p_bipbuffer_info->buffer_len;
}

/*
*函数名称：write_data_to_bipbuffer
*函数功能: 向缓冲区写入数据
*参数说明：buff:要写入的数据，size:要写入的大小
*返 回 值：实际写入的大小
*/
unsigned long write_data_to_bipbuffer(char *buff, unsigned long size)
{
char *data = NULL;
unsigned long actually_size = 0;

if (data != NULL && actually_size > 0)
{
commit_data(actually_size);
memcpy(data, buff,actually_size);
return actually_size;
}

return 0;
}

/*
*函数功能: 从缓冲区读出数据
*参数说明：buff:要读出的数据，size:要要读出的大小
*返 回 值：实际读出的大小
*/
unsigned long read_data_from_bipbuffer(char *buff, unsigned long size)
{
char *data = NULL;
unsigned long actually_size = 0;

data = (char *)get_contiguous_block(&actually_size);
if (data !=NULL && actually_size>0)
{
if (actually_size <= size)
size = actually_size;
else
size = size;

commit_block(size);
memcpy(buff, data, size);
memset(data, 0x00, size);
return size;
}

return 0;
}

/***************************************************************
*
*for debug
*2012-03-24
****************************************************************/
void printf_memory(void)
{
unsigned long i=0;
printf("memory:\n");
for (i=0; i<p_bipbuffer_info->buffer_len; i++)
printf("%02X ", buff[i]&0xFF);
printf("\n---------------\n");
}


#ifndef __BIPBUFFER_H_
#define __BIPBUFFER_H_

int init_bipbuffer(unsigned long size);
int destroy_bipbuffer(void);

unsigned long write_data_to_bipbuffer(char *buff, unsigned long size);
unsigned long read_data_from_bipbuffer(char *buff, unsigned long size);

void printf_memory(void);
#endif

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

#include "bipbuffer.h"

#define BUFF_LEN 100
int main(void)
{
char buff[BUFF_LEN];
unsigned long i = 0;
unsigned long size = 0;

if (init_bipbuffer(2*BUFF_LEN) != 0)
{
printf("\nallocate bip buffer err!");
return -1;
}

memset(buff, 0x66, BUFF_LEN);
write_data_to_bipbuffer(buff, BUFF_LEN);

for (i=0; i<size; i++)
printf("%02X ", buff[i]);
printf("\n====================================\n");

memset(buff, 0xFF, BUFF_LEN);
write_data_to_bipbuffer(buff, BUFF_LEN);
write_data_to_bipbuffer(buff, BUFF_LEN/2);

for (i=0; i<size; i++)
printf("%02X ", buff[i]);
printf("\n====================================\n");

printf("\n\n\n\n");
printf_memory();

fail:
destroy_bipbuffer();
return 0;
}


#### 高效的两段式循环缓冲区──BipBuffer（3）

2009-12-16 13:55:00

#### 高效的两段式循环缓冲区──BipBuffer（2）

2009-12-16 13:39:00

#### 圆形缓冲区（循环buffer）实现

2016-05-20 18:05:00

#### [嵌入式开发模块]环形缓冲区/循环队列 C语言实现

2017-06-22 15:14:26

#### 说说循环缓冲区（Ring Buffer）

2013-11-28 21:04:54

#### 循环缓冲区

2012-04-07 17:06:00

#### 环形缓冲区

2012-10-09 10:08:57

#### 环形缓冲区的设计与实现

2010-04-23 13:17:00

#### 串口实用的循环缓冲区

2017-10-19 18:17:30

#### 循环缓存区之C++实现

2017-09-06 22:12:07