環形緩衝器

環形緩衝器

環形緩衝器(ringr buffer),也稱作圓形佇列(circular queue),循環緩衝區(cyclic buffer),圓形緩衝區(circula buffer),是一種用於表示一個固定尺寸、頭尾相連的緩衝區的數據結構,適合快取數據流。

簡介

在通信程式中,經常使用環形緩衝器作為數據結構來存放通信中傳送和接收的數據。環形緩衝區是一個先進先出的循環緩衝區,可以向通信程式提供對緩衝區的互斥訪問。

用法

圓形緩衝區的一個有用特性是:當一個數據元素被用掉後,其餘數據元素不需要移動其存儲位置。相反,一個非圓形緩衝區(例如一個普通的佇列)在用掉一個數據元素後,其餘數據元素需要向前搬移。換句話說,圓形緩衝區適合實現先進先出緩衝區,而非圓形緩衝區適合後進先出緩衝區。

圓形緩衝區適合於事先明確了緩衝區的最大容量的情形。擴展一個圓形緩衝區的容量,需要搬移其中的數據。因此一個緩衝區如果需要經常調整其容量,用鍊表實現更為合適。

寫操作覆蓋圓形緩衝區中未被處理的數據在某些情況下是允許的。特別是在多媒體處理時。例如,音頻的生產者可以覆蓋掉音效卡尚未來得及處理的音頻數據。

工作過程

一個圓形緩衝區最初為空並有預定的長度。例如,這是一個具有七個元素空間的圓形緩衝區,其中底部的單線與箭頭表示“頭尾相接”形成一個圓形地址空間:

環形緩衝器 環形緩衝器

假定1被寫入緩衝區中部(對於圓形緩衝區來說,最初的寫入位置在哪裡是無關緊要的):

插入一個數據 插入一個數據

再寫入2個元素,分別是2 & 3 — 被追加在1之後:

添加元素 添加元素

如果兩個元素被處理,那么是緩衝區中最老的兩個元素被移除。在本例中,1 & 2被移除,緩衝區中只剩下3:

除去兩個元素 除去兩個元素

如果緩衝區中有7個元素,則是滿的:

填滿緩衝區 填滿緩衝區

如果緩衝區是滿的,又要寫入新的數據,一種策略是覆蓋掉最老的數據。此例中,2個新數據— A & B — 寫入,覆蓋了3 & 4:

覆蓋數據 覆蓋數據

也可以採取其他策略,禁止覆蓋緩衝區的數據,採取返回一個錯誤碼或者拋出異常。最終,如果從緩衝區中移除2個數據,不是3 & 4 而是 5 & 6 。因為 A & B 已經覆蓋了3 & 4:

移除數據 移除數據

圓形緩衝區工作機制

由於計算機記憶體是線性地址空間 ,因此圓形緩衝區需要特別的設計才可以從邏輯上實現。

讀指針與寫指針

一般的,圓形緩衝區需要4個指針 :

•在記憶體中實際開始位置;

•在記憶體中實際結束位置,也可以用緩衝區長度代替;

•存儲在緩衝區中的有效數據的開始位置(讀指針);

•存儲在緩衝區中的有效數據的結尾位置(寫指針)。

讀指針、寫指針可以用整型值來表示。下例為一個未滿的緩衝區的讀寫指針:

未滿緩衝區 未滿緩衝區

下例為一個滿的緩衝區的讀寫指針:

滿的緩衝區 滿的緩衝區

區分緩衝區滿或者空

緩衝區是滿、或是空,都有可能出現讀指針與寫指針指向同一位置:

指針在同一位置 指針在同一位置

有多種策略用於檢測緩衝區是滿、或是空.

總是保持一個存儲單元為空

環形緩衝器 環形緩衝器

緩衝區中總是有一個存儲單元保持未使用狀態。緩衝區最多存入 個數據。如果讀寫指針指向同一位置,則緩衝區為空。如果寫指針位於讀指針的相鄰後一個位置,則緩衝區為滿。這種策略的優點是簡單、魯棒;缺點是語義上實際可存數據量與緩衝區容量不一致,測試緩衝區是否滿需要做取餘數計算。

使用數據計數

這種策略不使用顯式的寫指針,而是保持著緩衝區記憶體儲的數據的計數。因此測試緩衝區是空是滿非常簡單;對性能影響可以忽略。缺點是讀寫操作都需要修改這個存儲數據計數,對於多執行緒訪問緩衝區需要並發控制。

鏡像指示位

環形緩衝器 環形緩衝器
環形緩衝器 環形緩衝器
環形緩衝器 環形緩衝器
環形緩衝器 環形緩衝器
環形緩衝器 環形緩衝器
環形緩衝器 環形緩衝器
環形緩衝器 環形緩衝器
環形緩衝器 環形緩衝器

緩衝區的長度如果是n,邏輯地址空間則為至;那么,規定至為鏡像邏輯地址空間。本策略規定讀寫指針的地址空間為至,其中低半部分對應於常規的邏輯地址空間,高半部分對應於鏡像邏輯地址空間。當指針值大於等於時,使其折返(wrapped)到。使用一位表示寫指針或讀指針是否進入了虛擬的鏡像存儲區:置位表示進入,不置位表示沒進入還在基本存儲區。

鏡像指示位 鏡像指示位

在讀寫指針的值相同情況下,如果二者的指示位相同,說明緩衝區為空;如果二者的指示位不同,說明緩衝區為滿。這種方法優點是測試緩衝區滿/空很簡單;不需要做取餘數操作;讀寫執行緒可以分別設計專用算法策略,能實現精緻的並發控制。 缺點是讀寫指針各需要額外的一位作為指示位。

如果緩衝區長度是2的冪,則本方法可以省略鏡像指示位。如果讀寫指針的值相等,則緩衝區為空;如果讀寫指針相差n,則緩衝區為滿,這可以用條件表達式(寫指針 == (讀指針 異或 緩衝區長度))來判斷。

讀寫計數

用兩個有符號整型變數分別保存寫入、讀出緩衝區的數據數量。其差值就是緩衝區中尚未被處理的有效數據的數量。這種方法的優點是讀執行緒、寫執行緒互不干擾;缺點是需要額外兩個變數。

記錄最後的操作

使用一位記錄最後一次操作是讀還是寫。讀寫指針值相等情況下,如果最後一次操作為寫入,那么緩衝區是滿的;如果最後一次操作為讀出,那么緩衝區是空。 這種策略的缺點是讀寫操作共享一個標誌位,多執行緒時需要並發控制。

POSIX最佳化實現

Linux核心的kfifo

在Linux核心檔案kfifo.h和kfifo.c中,定義了一個先進先出圓形緩衝區實現。如果只有一個讀執行緒、一個寫執行緒,二者沒有共享的被修改的控制變數,那么可以證明這種情況下不需要並發控制。kfifo就滿足上述條件。kfifo要求緩衝區長度必須為2的冪。讀、寫指針分別是無符號整型變數。把讀寫指針變換為緩衝區內的索引值,僅需要“按位與”操作:(指針值 按位與 (緩衝區長度-1))。這避免了計算代價高昂的“求余”操作。且下述關係總是成立:讀指針 + 緩衝區存儲的數據長度 == 寫指針

即使在寫指針達到了無符號整型的上界,上溢出後寫指針的值小於讀指針的值,上述關係仍然保持成立(這是因為無符號整型加法的性質)。 kfifo的寫操作,首先計算緩衝區中當前可寫入存儲空間的數據長度:len = min{待寫入數據長度, 緩衝區長度 - (寫指針 - 讀指針)}然後,分兩段寫入數據。第一段是從寫指針開始向緩衝區末尾方向;第二段是從緩衝區起始處寫入餘下的可寫入數據,這部分可能數據長度為0即並無實際數據寫入。

相關詞條

熱門詞條

聯絡我們