高效的兩段式循環緩衝區──BipBuffer

Simon Cooke,美國 (原作者)
北京理工大學 20981 陳罡(翻譯)

寫在前面的話:

    循環緩衝區是一個非常常用的數據存儲結構,已經被廣泛地用於連續、流數據的存儲和通信應用中。對於循環緩衝區,傳統的操作方法是開闢一塊連續的存儲區,不斷地寫入數據,當寫入到存儲區的末尾的時候,再從存儲區的首部再開始寫入數據,由此不斷地重複下去構成了循環緩衝區。偶曾經寫過很多循環緩衝區,也看過很多人編寫的循環緩衝區,但是拜讀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.

在很久以前,計算機要簡單得多,它們並沒有64位的總線,甚至沒有真正的16位的寄存器,儘管你可能會相信真的有一對寄存器在做減法計算(譯者:這句話很繞口,應該是作者在以一種調侃的語氣跟讀者交流,也不知道偶理解得對不對。作者的意思是暗指計算機是沒有減法的,所謂減法就是在進行補碼的加法運算,但是對於編程人員來說,由於彙編指令集裏面是有SUB指令的,或許有些初級的開發者根據這個指令會想當然地認爲寄存器之間在做減法運算。)。在這個“石器”時代,大師們使用彙編語言編寫程序(譯者,石刀、石斧?),他們會嘲笑那些不知道如何使用進位標誌位來進行編程的開發者。


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.

在那個時代裏,一流的黑客們想方設法地“壓榨”計算機在每一條指令週期的運算能力。舉個例子來說,一個簡單的終端通信程序(譯者,這裏的終端通信,指的是基於rs232的串行通信,在作者所指的那個時代,應該還沒有所謂互聯網這樣的東西存在),較新的rs232串行控制器可以通過自動處理RTS和CTS信號線來控制數據流向(但是這帶來了一定程度的帶寬資源浪費)。正如RTS和CTS這樣的名字所代表的意思一樣,串行通信的數據連接需要不斷地處理控制器開始和停止信號,而不是採用類似“流”的方式連續不斷地傳輸數據。於是,在控制卡和系統之間,我們通常可以找到一個叫做FIFO(譯者,可以理解爲數據結構中的先進先出型的隊列)東西。這個或許是最簡單的循環緩衝區的雛形,它通常只有幾個到幾十個字節左右的長度,但是它的出現,意味着整個系統可以流暢地運行,不需要實時地檢查(譯者,也就是所謂的輪循)是否有新的數據到達,或者應用程序的執行過程不斷地被串行控制器的硬件中斷(譯者,這裏的中斷應該跟微機原理中學到的中斷類似)所打擾。

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.

絕大多數的FIFO都是在芯片上完成的,但是開發者們也會把這種理念用於他們的代碼中,尤其是當某些通信連續性很糟糕的場合,需要開發者多次接收數據,然後一次性讀取出來處理的時候,很多人想到了循環緩衝區。有了它的幫助,開發者可以在等待的這段時間裏讓計算機做一些其它事情,例如滾動控制檯輸出或者解碼GIF圖片之類的。

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.

正如我所提到的,FIFO是非常簡單的循環緩衝區,而且絕大部分都是非常簡單的實現;它們的長度一般都是2的n次方,這樣就可以允許對指針進行簡單的溢出判斷和處理完成指針重新指向緩衝區的起始位置。FIFO的邏輯可以很容易地通過頭指針和尾指針的值來判斷緩衝區是“空”還是“滿”——頭指針和尾指針的數值相等,代表緩衝區爲空;頭指針的數值如果比尾指針大1,則代表緩衝區滿。

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.

在老式的8位系統裏面實現上述FIFO是非常簡單的事情。找兩個8位的寄存器構成一個16位的寄存器對,分配一段內存(取256的倍數)來保存FIFO數據,然後,讓寄存器對指向該段內存的起始地址(譯者,8位的系統,一般尋址空間是16位的,作者的意思是要用兩個8位的寄存器來保存16位的內存地址,256的倍數代表了一個對齊問題,如果取256的倍數的話,就會讓16位的寄存器對,只有高8位是有數值的,低8位是從0開始的),注意不要去碰高8位的寄存器,就讓它保留內存地址的值即可;然後可以使用8位模式來操作低8位寄存器對16位的寄存器對指向的內存地址進行FIFO數據寫入操作,把低8位的寄存器做爲0-255的索引,每寫入一個字節,就把低8位的寄存器加1,一旦超過了255,低8位的寄存器就會溢出,讓低8位寄存器重新從0開始,這樣就由硬件自動完成了循環緩衝區指針的調整。用這種方式就爲你提供了只需要一個指令週期就可以完成操作的256個字節的循環緩衝區(在Zilog Z80系統上面,需要4個指令週期,這是在該系統裏可以得到的最小的執行單位)。在這個實現中,由於是採用硬件溢出的方式來調整循環緩衝區的指針,因此,根本不必擔心會溢出,會把數據寫到其它的內存裏面。(譯者,這可能是可以用軟件實現的效率最高、安全性最好的循環緩衝區了。)

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.
很不幸,對於現代windows程序開發人員來說,已經沒有可能找到一種效率可以與早先8位機時代的FIFO相媲美的循環緩衝區的“完美”解決方案了。當然了,你可以深入研究彙編語言(你可以知道編譯器是如何把寄存器和程序中的數值映射起來,然後做某種優化。。。總之我從來沒有看到過一個能夠讓我改變我的這個看法的彙編解決方案),但是絕大多數人沒有時間去挖掘彙編語言的潛力。而且,我們現代的操作系統都採用的是32位的寄存器,依靠寄存器加1,然後利用硬件溢出來達到循環利用緩衝區的做法,基本上已經不太現實了。現代的操作系統會利用cache(緩存)技術,管道延遲技術,printer fires, rains of frog等等來擴大尋址的空間。(譯者,這後面兩個技術不知道是什麼意思,還望知道的朋友提示一下。)


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.
我的第一個在現代的操作系統中實現高效的FIFO循環緩衝區的設想是基於一個模糊的目標,希望能夠欺騙虛擬內存系統,在當前緩衝區的後面做一個鏡像的緩衝區,這樣一旦對這段緩衝區寫入數據超過了內存的邊界,數據會自動寫入到當前當前緩衝區的起始位置去。這樣,就可以仍然構成一個循環使用的存儲區,而且內存拷貝等的操作不需要檢查當前指針是否到達了內存區的邊界——這是因爲進程的地址空間已經被修改過了,緩衝區末尾的再下一個字節的地址恰恰就是緩衝區的開始的地址。(譯者,作者的這個設想確實很有趣,但是估計現代的操作系統還沒有開放到這個程度,估計用linux通過修改一些內核代碼,應該是可以做到作者這個想法的)


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.
這個鏡像技術的設想或許真的會工作,但是由於一些系統限制性的原因,我決定不去自己實現它(雖然現在沒有,我肯定將來的什麼時候我會爲它找到一種應用方式)。這個想法背後,意味着程序需要維護兩塊並列的虛擬內存,在兩個虛擬內存中映射的是同一個臨時文件。Instant mirroring技術(譯者,這或許是作者一時激動,給這個設想起的名字吧。。。)最終可以允許用戶無限制地向緩衝區寫入數據、讀取數據。


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?
不幸的是,儘管它確實應該工作(再次聲明,我沒有嘗試去做這個實驗),但是會引起另外一個問題,也就是說文件只能被映射到64kb的邊界(也許在更大的內存系統中會大一些)。這就意味着緩衝區最小也需要64kb的大小,並且會用掉虛擬地址空間的128kb的尋址空間。無論如何,這取決於你的應用程序的規模,這也許是一個可行的技術,但是我從沒看到過爲1000個socket端口提供服務的程序有采用這種技術的苗頭。(譯者,作者很無奈,畢竟想法是好的,但是真實的服務器開發,需要的是可靠、穩定、以及高效,沒人願意爲了測試那些還在設想中的技術而賭上自己的schedule :P)。

既然如此,該怎麼辦呢?如果這種mirroring的技術不能夠工作,我們如何找到一種在效率上最最接近它的循環緩衝區的實現方案呢?假設我們可以做出這樣的接近於上述方案的循環緩衝區,那麼我們該如何做呢?

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.
圍繞着臨時緩衝區的使用方法,有很多種不同的做法;最簡單的一種就是直接把數據拷貝出來使用(這當然是一種效率非常低下的做法),或者動態擴展緩衝區的長度(這涉及到系統的內存分配和釋放,是效率更加低下的做法)。而使用循環緩衝區,可以總是有可用的內存來寫入數據;數據可以任意拷貝,可以直接通過指針來對數據進行存取等等。緩衝區只需要分配一次即可在其整個生存週期中發揮作用,不需要從新分配,不需要拷貝數據(譯者,這裏的不需要拷貝數據是指,Bip-Buffer是支持直接用指針來寫入數據的,因此不需要內存拷貝之類的操作。)
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.
人們可以通過簡單的分配一塊內存,小心地維護指向其的指針來實現一個循環緩衝區。當數據寫入到緩衝區的末尾的時候,應該將數據寫入指針調整到緩衝區起始,這個看似簡單的操作影響到對緩衝區的所有操作,無論是把數據寫入還是讀出。緩衝區中已用數據的長度的計算要比往常複雜一點,效率會稍微低一點(但是這一點可以通過inline函數很輕易地解決),可以不足爲慮。
不幸的是,絕大多數的api調用是不支持循環緩衝區的,很多情況下,人們不得不爲這些api傳入連續的裝滿數據的內存區,在api函數使用這塊內存區的時候是無法調整其讀取或寫入指針到循環緩衝區的開頭的。那該如何是好?彆着急,這就是Bip-Buffer存在的理由。
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.
如果人們不能直接把循環緩衝區直接做爲參數傳入api函數,那麼就需要另外一種解決方案——這種方案是一個既要擁有與循環緩衝區一樣的優點,又要沒有上述缺點的方案。建立一個循環緩衝區,但是在緩衝區內部將分配的兩次內存,內部維護個兩個區域;或者簡單地來說,將傳統的爲循環緩衝區分配的一塊內存分裂成兩塊內存緩衝區來使用,這裏的分裂(bi-partite)也是Bip-Buffer前面的Bip這個縮寫的由來。兩塊內存區都是從左往右來寫入數據的,在申請緩衝區的時候,當一個緩衝區滿了,就會馬上從另外一個內存區的起始地點開始寫入,這樣就避免了調整頭指針的問題。下面的圖例就明確地顯示了它是如何工作的:
The buffer starts out empty, with no regions present (figure 1). (eg. immediately after calling AllocatedBuffer)
整個緩衝區開始的時候是空的,沒有分裂成兩個區域(如圖1所示,是調用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)
然後,當第一次將數據放到緩衝區以後(如圖2所示,這是調用了Commit()函數以後的樣子),創建了第一個區域(圖中的'A'區域)

Data is added to the region, extending it to the right (figure 3).
當有數據繼續寫入的時候,都會被追加到區域A的數據的後面,使其向右不斷伸展(如圖3所示)

For the first time now, we remove data from the buffer (figure 4). (see the DecommitBlock call described below)
第一次從緩衝區中讀出數據的樣子(如圖4所示,參見下面的關於DecommitBlock()函數的說明)
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.
這種形式的寫入和讀出過程會持續下去,直到區域到達了緩衝區的末尾(如圖4所示)。一旦在區域A左側的可用空閒空間要大於區域A右側的空閒空間,第二個區域(我們叫做區域B)就被創建出來,從此以後的內存分配就從這個區域B來分配(如圖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.
如果繼續使用緩衝區的空間,我們就會看到如圖6所示的樣子,區域B在不斷擴展,將數據追加到區域B的後面。如果用光區域B與區域A之間的空間(如圖7所示),那麼就真的沒有內存可分配了,直到有一些數據從循環緩衝區中讀出。

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)
如果從緩衝區中讀取更多的數據出來(假設整個區域A的數據都被讀取出來),我們完全把區域A中存儲的數據用光了,這時候區域A就完全空了,我們就不再需要維護兩個獨立的區域了,這時,區域B就獲得了整個緩衝區,可以繼續追加數據,同時也支持從區域B中向外讀取數據了(如圖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.
如果從緩衝區中讀取更多的數據(如圖9所示),我們最終又會得到類似圖4所示的存儲區使用狀態,然後繼續重複4,5,6,7,8這些狀態的切換,完成循環利用緩衝區。
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.
在絕大多數場合下,BipBuffer都可以充分利用全部緩衝區資源,而且即使到了緩衝區的末尾,也不需要任何數據拷貝或重新分配存儲空間來實現循環利用緩衝區。

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 tosize 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, andreserved 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. Thesize parameter is also updated with the length of the block. If no data is available, the function returns NULL (and thesize 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 asize 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 read_EOF;

bool StartUp
{
    // Allocate a buffer 8192 bytes in length
    if (!buffer.AllocateBuffer(8192)) return false;
    readEOF = 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);
    }
}
代碼:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <malloc.h>
  5. #include "bipbuffer.h"
  6. struct __bipbuffer_info{
  7. unsigned long size_b; //b空間大小
  8. unsigned long size_a; //a空間大小
  9. unsigned long offer_a; //b空間偏移
  10. unsigned long offer_b; //a空間偏移
  11. unsigned long buffer_len; //緩衝區大小
  12. unsigned long offer_reserve;//儲存空閒空間偏移
  13. unsigned long size_reserve; //儲存空閒空間大小
  14. char *bipbuffer_addr;
  15. };
  16. struct __bipbuffer_info *p_bipbuffer_info = NULL;
  17. /*
  18. *函數名稱:init_bipbuffer_struct
  19. *函數功能:初始化結構
  20. *參數說明:size 傳值
  21. *返 回 值:void
  22. */
  23. static void init_bipbuffer_struct(unsigned long size)
  24. {
  25. //初始化結構
  26. p_bipbuffer_info->size_a = 0;
  27. p_bipbuffer_info->size_b = 0;
  28. p_bipbuffer_info->offer_a = 0;
  29. p_bipbuffer_info->offer_b = 0;
  30. p_bipbuffer_info->offer_reserve = 0;
  31. p_bipbuffer_info->size_reserve = 0;
  32. p_bipbuffer_info->buffer_len = size;
  33. }
  34. /*
  35. *函數名稱:init_bipbuffer
  36. *函數功能:分配高效兩段式循環緩衝區
  37. *參數說明:size 要分配緩衝區的大小,一般是以頁爲單位
  38. *返 回 值:成功返回0,失敗返回負數
  39. */
  40. int init_bipbuffer(unsigned long size)
  41. {
  42. p_bipbuffer_info = (struct __bipbuffer_info *)malloc(sizeof(struct __bipbuffer_info));
  43. if (p_bipbuffer_info == NULL)
  44. return -1;
  45. //分配緩衝區大小
  46. p_bipbuffer_info->bipbuffer_addr = (char*)malloc(size);
  47. if (p_bipbuffer_info->bipbuffer_addr == NULL){
  48. free(p_bipbuffer_info);
  49. p_bipbuffer_info = NULL;
  50. return -2;
  51. }
  52. init_bipbuffer_struct(size);
  53. return 0;
  54. }
  55. /*
  56. *函數名稱:destroy_bipbuffer
  57. *函數功能:釋放緩衝區
  58. *參數說明:void
  59. *返 回 值:成功返回0
  60. */
  61. int destroy_bipbuffer(void)
  62. {
  63. if (p_bipbuffer_info->bipbuffer_addr){
  64. free(p_bipbuffer_info->bipbuffer_addr);
  65. p_bipbuffer_info->bipbuffer_addr = NULL;
  66. }
  67. if (p_bipbuffer_info){
  68. free(p_bipbuffer_info);
  69. p_bipbuffer_info = NULL;
  70. }
  71. return 0;
  72. }
  73. /*
  74. *函數名稱:get_space_after_a
  75. *函數功能:獲得內存塊A後空閒緩衝區的大小
  76. *參數說明:void
  77. *返 回 值:空閒緩衝區的大小
  78. */
  79. static unsigned long get_space_after_a(void)
  80. {
  81. return p_bipbuffer_info->buffer_len -
  82. p_bipbuffer_info->offer_a - p_bipbuffer_info->size_a;
  83. }
  84. /*
  85. *函數名稱:get_b_free_space
  86. *函數功能:獲得內存塊B空閒緩衝區的大小
  87. *參數說明:void
  88. *返 回 值:空閒緩衝區的大小
  89. */
  90. static unsigned long get_b_free_space(void)
  91. {
  92. return p_bipbuffer_info->offer_a -
  93. p_bipbuffer_info->offer_b - p_bipbuffer_info->size_b;
  94. }
  95. /*
  96. *函數名稱:get_reserve_addr
  97. *函數功能:獲得空閒緩衝區的地址
  98. *參數說明:want_size:需要的大小,actually_size實際能給出的大小
  99. *返 回 值:有空閒空間返回地址,沒有則返回NULL
  100. */
  101. static char *get_reserve_addr(unsigned long want_size, unsigned long *actually_size)
  102. {
  103. unsigned long free_space_size;
  104. /*
  105. We always allocate on B if B exists;
  106. this means we have two blocks and our buffer is filling.
  107. */
  108. if (p_bipbuffer_info->size_b)
  109. {
  110. free_space_size = get_b_free_space();
  111. if (free_space_size == 0)
  112. return NULL;
  113. //多餘的空間大於需要的空間,說明有多餘的空間
  114. if (want_size < free_space_size)
  115. free_space_size = want_size;
  116. p_bipbuffer_info->offer_reserve = free_space_size;
  117. *actually_size = free_space_size;
  118. p_bipbuffer_info->offer_reserve = p_bipbuffer_info->offer_b + p_bipbuffer_info->size_b;
  119. return p_bipbuffer_info->bipbuffer_addr + p_bipbuffer_info->offer_reserve;
  120. }
  121. else
  122. {
  123. /* Block b does not exist, so we can check
  124. if the space after a is bigger than the space before A,
  125. and allocate the bigger one.
  126. */
  127. /*
  128. 如果內存塊B不存在,檢查空閒空間是否比內存塊A大,分配最大的
  129. */
  130. free_space_size = get_space_after_a();
  131. printf("\nfree space:%ld", free_space_size);
  132. if (free_space_size >= p_bipbuffer_info->offer_a)//內存塊A後有更大的空間
  133. {
  134. if (free_space_size == 0)
  135. return NULL;
  136. //如果需要分配的空間小於空閒的空間
  137. if (want_size < free_space_size)
  138. free_space_size = want_size;
  139. p_bipbuffer_info->size_reserve = free_space_size;
  140. *actually_size = free_space_size;
  141. p_bipbuffer_info->offer_reserve = p_bipbuffer_info->offer_a + p_bipbuffer_info->size_a;
  142. return p_bipbuffer_info->bipbuffer_addr + p_bipbuffer_info->offer_reserve;
  143. }
  144. else//空閒空間不夠
  145. {
  146. if (p_bipbuffer_info->offer_a == 0)
  147. return NULL;
  148. if (p_bipbuffer_info->offer_a < want_size)
  149. want_size = p_bipbuffer_info->offer_a;
  150. p_bipbuffer_info->size_reserve = want_size;
  151. *actually_size = want_size;
  152. p_bipbuffer_info->offer_reserve = 0;
  153. return p_bipbuffer_info->bipbuffer_addr;
  154. }
  155. }
  156. return NULL;
  157. }
  158. /*
  159. *函數名稱:commit_data
  160. *函數功能:數據寫入後,修改緩衝區的指針
  161. *參數說明:size:大小
  162. *返 回 值:void
  163. */
  164. static void commit_data(unsigned long size)
  165. {
  166. if (size == 0)
  167. {
  168. // decommit any reservation
  169. p_bipbuffer_info->size_reserve = 0;
  170. p_bipbuffer_info->offer_reserve = 0;
  171. return;
  172. }
  173. /* If we try to commit more space than we asked for,
  174. clip to the size we asked for.
  175. */
  176. if (size > p_bipbuffer_info->size_reserve)
  177. size = p_bipbuffer_info->size_reserve;
  178. // If we have no blocks being used currently, we create one in A.
  179. if (p_bipbuffer_info->size_a == 0 && p_bipbuffer_info->size_b == 0)
  180. {
  181. p_bipbuffer_info->offer_a = p_bipbuffer_info->offer_reserve;
  182. p_bipbuffer_info->size_a = size;
  183. p_bipbuffer_info->offer_reserve = 0;
  184. p_bipbuffer_info->size_reserve = 0;
  185. return;
  186. }
  187. if (p_bipbuffer_info->offer_reserve ==
  188. p_bipbuffer_info->size_a + p_bipbuffer_info->offer_a)
  189. p_bipbuffer_info->size_a += size;
  190. else
  191. p_bipbuffer_info->size_b += size;
  192. p_bipbuffer_info->offer_reserve = 0;
  193. p_bipbuffer_info->size_reserve = 0;
  194. }
  195. /*
  196. *函數名稱:get_contiguous_block
  197. *函數功能:獲得可讀的數據
  198. *參數說明:size:大小
  199. *返 回 值:返回可讀的首地址
  200. */
  201. static char *get_contiguous_block(unsigned long *size)
  202. {
  203. if (p_bipbuffer_info->size_a == 0)
  204. {
  205. *size = 0;
  206. return NULL;
  207. }
  208. *size = p_bipbuffer_info->size_a;
  209. return p_bipbuffer_info->bipbuffer_addr + p_bipbuffer_info->offer_a;
  210. }
  211. /*
  212. *函數名稱:commit_block
  213. *函數功能:數據讀出後,修改緩衝區的指針
  214. *參數說明:size:大小
  215. *返 回 值:void
  216. */
  217. static void commit_block(unsigned long size)
  218. {
  219. if (size >= p_bipbuffer_info->size_a)
  220. {
  221. p_bipbuffer_info->offer_a = p_bipbuffer_info->offer_b;
  222. p_bipbuffer_info->size_a = p_bipbuffer_info->size_b;
  223. p_bipbuffer_info->offer_a = 0;
  224. p_bipbuffer_info->size_b = 0;
  225. }
  226. else
  227. {
  228. p_bipbuffer_info->size_a -= size;
  229. p_bipbuffer_info->offer_a += size;
  230. }
  231. }
  232. /*
  233. *函數名稱:get_committed_size
  234. *函數功能:獲得塊A和塊B的總大小
  235. *參數說明:void
  236. *返 回 值:大小
  237. */
  238. unsigned long get_committed_size(void)
  239. {
  240. return p_bipbuffer_info->size_a + p_bipbuffer_info->size_b;
  241. }
  242. /*
  243. *函數名稱:get_reservation_size
  244. *函數功能:獲得存儲的大小
  245. *參數說明:void
  246. *返 回 值:大小
  247. */
  248. unsigned long get_reservation_size(void)
  249. {
  250. return p_bipbuffer_info->size_reserve;
  251. }
  252. /*
  253. *函數名稱:get_buffer_size
  254. *函數功能:獲得整個緩衝區的大小
  255. *參數說明:void
  256. *返 回 值:大小
  257. */
  258. unsigned long get_buffer_size(void)
  259. {
  260. return p_bipbuffer_info->buffer_len;
  261. }
  262. /*
  263. *函數名稱:write_data_to_bipbuffer
  264. *函數功能: 向緩衝區寫入數據
  265. *參數說明:buff:要寫入的數據,size:要寫入的大小
  266. *返 回 值:實際寫入的大小
  267. */
  268. unsigned long write_data_to_bipbuffer(char *buff, unsigned long size)
  269. {
  270. char *data = NULL;
  271. unsigned long actually_size = 0;
  272. data = (char*)get_reserve_addr(size, &actually_size);
  273. if (data != NULL && actually_size > 0)
  274. {
  275. commit_data(actually_size);
  276. memcpy(data, buff,actually_size);
  277. return actually_size;
  278. }
  279. return 0;
  280. }
  281. /*
  282. *函數名稱:read_data_from_bipbuffer
  283. *函數功能: 從緩衝區讀出數據
  284. *參數說明:buff:要讀出的數據,size:要要讀出的大小
  285. *返 回 值:實際讀出的大小
  286. */
  287. unsigned long read_data_from_bipbuffer(char *buff, unsigned long size)
  288. {
  289. char *data = NULL;
  290. unsigned long actually_size = 0;
  291. data = (char *)get_contiguous_block(&actually_size);
  292. if (data !=NULL && actually_size>0)
  293. {
  294. if (actually_size <= size)
  295. size = actually_size;
  296. else
  297. size = size;
  298. commit_block(size);
  299. memcpy(buff, data, size);
  300. memset(data, 0x00, size);
  301. return size;
  302. }
  303. return 0;
  304. }
  305. /***************************************************************
  306. *
  307. *for debug
  308. *2012-03-24
  309. ****************************************************************/
  310. void printf_memory(void)
  311. {
  312. unsigned long i=0;
  313. char *buff = (char*)p_bipbuffer_info->bipbuffer_addr;
  314. printf("memory:\n");
  315. for (i=0; i<p_bipbuffer_info->buffer_len; i++)
  316. printf("%02X ", buff[i]&0xFF);
  317. printf("\n---------------\n");
  318. }
#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;	//儲存空閒空間大小
	char *bipbuffer_addr;
};
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;
		
	//分配緩衝區大小
	p_bipbuffer_info->bipbuffer_addr = (char*)malloc(size);
	if (p_bipbuffer_info->bipbuffer_addr ==  NULL){
		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->bipbuffer_addr){
		free(p_bipbuffer_info->bipbuffer_addr);
		p_bipbuffer_info->bipbuffer_addr = NULL;
	}
	
	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;
}
	
/*
 *函數名稱:get_reserve_addr
 *函數功能:獲得空閒緩衝區的地址
 *參數說明: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;
		return p_bipbuffer_info->bipbuffer_addr + p_bipbuffer_info->offer_reserve;
	}
	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;
			return p_bipbuffer_info->bipbuffer_addr + p_bipbuffer_info->offer_reserve;
		}
		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 p_bipbuffer_info->bipbuffer_addr;
		}
	}
	
	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;
	return p_bipbuffer_info->bipbuffer_addr + p_bipbuffer_info->offer_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;
	
	data = (char*)get_reserve_addr(size, &actually_size);
	if (data != NULL && actually_size > 0)
	{
		commit_data(actually_size);
		memcpy(data, buff,actually_size);
		return actually_size;
	}
	
	return 0;
}

/*
 *函數名稱:read_data_from_bipbuffer
 *函數功能: 從緩衝區讀出數據
 *參數說明: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;
	char *buff = (char*)p_bipbuffer_info->bipbuffer_addr;
	printf("memory:\n");
	for (i=0; i<p_bipbuffer_info->buffer_len; i++)
		printf("%02X ", buff[i]&0xFF);
	printf("\n---------------\n");
}


  1. #ifndef __BIPBUFFER_H_
  2. #define __BIPBUFFER_H_
  3. int init_bipbuffer(unsigned long size);
  4. int destroy_bipbuffer(void);
  5. unsigned long write_data_to_bipbuffer(char *buff, unsigned long size);
  6. unsigned long read_data_from_bipbuffer(char *buff, unsigned long size);
  7. void printf_memory(void);
  8. #endif
#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

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <malloc.h>
  4. #include <string.h>
  5. #include "bipbuffer.h"
  6. #define BUFF_LEN 100
  7. int main(void)
  8. {
  9. char buff[BUFF_LEN];
  10. unsigned long i = 0;
  11. unsigned long size = 0;
  12. if (init_bipbuffer(2*BUFF_LEN) != 0)
  13. {
  14. printf("\nallocate bip buffer err!");
  15. return -1;
  16. }
  17. memset(buff, 0x66, BUFF_LEN);
  18. write_data_to_bipbuffer(buff, BUFF_LEN);
  19. size = read_data_from_bipbuffer(buff, BUFF_LEN/2);
  20. printf("\n have %ld read:", size);
  21. for (i=0; i<size; i++)
  22. printf("%02X ", buff[i]);
  23. printf("\n====================================\n");
  24. memset(buff, 0xFF, BUFF_LEN);
  25. write_data_to_bipbuffer(buff, BUFF_LEN);
  26. write_data_to_bipbuffer(buff, BUFF_LEN/2);
  27. size = read_data_from_bipbuffer(buff, BUFF_LEN);
  28. printf("\n have %ld read:", size);
  29. for (i=0; i<size; i++)
  30. printf("%02X ", buff[i]);
  31. printf("\n====================================\n");
  32. printf("\n\n\n\n");
  33. printf_memory();
  34. fail:
  35. destroy_bipbuffer();
  36. return 0;
  37. }
發佈了48 篇原創文章 · 獲贊 10 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章