LwRB 使用說明

LwRB 使用說明

LwRB (lightweight ring buffer) 是一個輕量級的環(huán)形緩沖區(qū),除了支持 ring buffer 常規(guī)的讀寫操作,它還有自己的特色,比如支持讀寫事件通知,支持 DMA 零拷貝收發(fā)數(shù)據(jù)。

下面依次介紹相應(yīng)功能的使用方式:

LwRB 原理淺析

詳細(xì)內(nèi)容請參考 LwRB - How it works,以下僅寫出我的理解。

LwRB 內(nèi)部有一塊地址連續(xù)的緩沖區(qū),這個緩沖區(qū)是用戶調(diào)用 lwrb_init() 時指定的,接口如下:

uint8_t     lwrb_init(LWRB_VOLATILE lwrb_t* buff, void* buffdata, size_t size);

而且 LwRB 內(nèi)部維護(hù)了一個讀(R)、寫(W)指針,分別指向下一個可讀、下一個可寫的位置。寫入一個字節(jié)數(shù)據(jù)時,將數(shù)據(jù)放入寫指針的位置,并將寫指針加 1 ,若超出緩沖區(qū)范圍,則回滾到緩沖區(qū)起始處。

Different buffer corner cases

例如上圖例程,緩沖區(qū)大小 S 為 8 :

  • 初始時:RW 相等,代表 LwRB 為空。
  • 寫入 4 個字節(jié):W 移動到 4 的位置,此時 LwRB 存儲了 4 個字節(jié)。
  • 寫入 3 個字節(jié):W 移動到 7 的位置,此時 LwRB 存儲了 7 個字節(jié),而且 W == R-1 , 或者說 W == (R + S - 1) % S,代表 LwRB 已滿。
  • 讀出 5 個字節(jié),并再次寫入 4 個字節(jié):W 移動到 3,R 移動到 5,此時 LwRB 存儲了 6 個字節(jié)。
  • 寫入 1 個字節(jié):W 移動到 4,此時 LwRB 存儲了 7 個字節(jié),而且 W == R-1 ,或者說 W == (R + S - 1) % S,代表 LwRB 已滿。

需要注意的是,lwrb 內(nèi)部 W == R 代表 LwRB 為空,W == (R + S - 1) % S 代表 LwRB 為滿。

讀寫操作

LwRB 里的讀寫操作接口為:

/* Read/Write functions */
size_t      lwrb_write(LWRB_VOLATILE lwrb_t* buff, const void* data, size_t btw);
size_t      lwrb_read(LWRB_VOLATILE lwrb_t* buff, void* data, size_t btr);
size_t      lwrb_peek(LWRB_VOLATILE lwrb_t* buff, size_t skip_count, void* data, size_t btp);
  • lwrb_write() 用于向 LwRB 里寫入數(shù)據(jù),并移動寫指針。
  • lwrb_read() 用于從 LwRB 里讀出數(shù)據(jù),并移動讀指針。
  • lwrb_peek() 用于從 LwRB 里讀出數(shù)據(jù),不移動讀指針。

這里需要區(qū)分 lwrb_read()lwrb_peek() 的不同之處,read 是真正地從 LwRB 里把數(shù)據(jù)取出來,會修改讀指針,read 之后該數(shù)據(jù)就被 LwRB 移除了;而 peek 操作從 LwRB 里讀數(shù)據(jù),但并不取出來,不會修改讀指針,peek 之后數(shù)據(jù)仍在 LwRB 里。

lwrb_peek() 函數(shù)還有一些特殊的用法,函數(shù)原型如下:

size_t      lwrb_peek(LWRB_VOLATILE lwrb_t* buff, size_t skip_count, void* data, size_t btp);
  • buff - LwRB 對象
  • skip_count - 跳過幾個數(shù)據(jù)再開始讀取
  • data - 讀取數(shù)據(jù)緩沖區(qū)
  • btp - 需要讀取的字節(jié)數(shù)(bytes to peek)

同時該函數(shù)返回實際讀取的字節(jié)數(shù)。以下來理解如何使用:

假設(shè) LwRB 內(nèi)部狀態(tài)如下:W 處于 4 的位置,R 處于 6 的位置。

image

此時我們想要從 LwRB 里讀取 4 個字節(jié),調(diào)用 lwrb_peek(&buff, 0, data, 4); ,則最終得到的數(shù)據(jù)為:6, 7, 0, 1 ,該函數(shù)內(nèi)部幫我們處理了讀指針回滾的情況,這是目前許多 ring buffer 不具備的功能。而且該函數(shù)還支持跳過最開始處的字節(jié),例如調(diào)用 lwrb_peek(&buff, 2, data, 4); ,則最終得到的數(shù)據(jù)為:0, 1, 2, 3,該函數(shù)內(nèi)部自動跳過了讀指針對開始處的 6, 7 這兩個字節(jié)數(shù)據(jù)。

例程可參考:getting_started.c 和 lwrb_peek.c 。

讀寫事件通知

詳細(xì)內(nèi)容請參考 LwRB - Events,以下僅寫出我的理解。

讀寫事件通知接口如下:

typedef void (*lwrb_evt_fn)(LWRB_VOLATILE struct lwrb* buff, lwrb_evt_type_t evt, size_t bp);
void        lwrb_set_evt_fn(LWRB_VOLATILE lwrb_t* buff, lwrb_evt_fn fn);

lwrb 的事件通知功能是用戶調(diào)用 lwrb_set_evt_fn() 向 lwrb 注冊一個回調(diào)函數(shù),然后 lwrb 每次進(jìn)行讀寫數(shù)據(jù)時,都會通過這個回調(diào)函數(shù)通知用戶當(dāng)前寫入了多少個字節(jié),或讀取了多少個字節(jié)。

該功能可用于打印日志,釋放信號量等,更多使用場景還待挖掘。

例程可參考:event.c 。

DMA 零拷貝收發(fā)數(shù)據(jù)

詳細(xì)內(nèi)容請參考 LwRB - DMA for embedded systems,以下僅寫出我的理解。

LwRB 提供的零拷貝功能,按照工作方式可分為兩種類型:

  • 從 LwRB 里零拷貝讀取數(shù)據(jù)
  • 向 LwRB 里零拷貝寫入數(shù)據(jù)

先介紹從 LwRB 里零拷貝讀取數(shù)據(jù),相關(guān)接口為:

/* Read data block management */
void*       lwrb_get_linear_block_read_address(LWRB_VOLATILE lwrb_t* buff);
size_t      lwrb_get_linear_block_read_length(LWRB_VOLATILE lwrb_t* buff);
size_t      lwrb_skip(LWRB_VOLATILE lwrb_t* buff, size_t len);

假設(shè)當(dāng)前 LwRB 內(nèi)部狀態(tài)如下圖所示:

image

LwRB 內(nèi)部緩沖區(qū)已滿,W 處于 4 的位置,R 處于 5 的位置。

接口 lwrb_get_linear_block_read_address() 可獲取當(dāng)前 LwRB 內(nèi)部緩沖區(qū)第一個可讀取字節(jié)的地址,例如上圖中的 R,而 lwrb_get_linear_block_read_length() 函數(shù)獲取當(dāng)前 LwRB 內(nèi)部緩沖區(qū)可讀取的,且地址連續(xù)的字節(jié)數(shù),例如上圖中會得到結(jié)果 3,表示可連續(xù)讀取后續(xù)的數(shù)據(jù) 5, 6, 7 。

利用這兩個函數(shù),我們就得到了一個指針 p 和一個大小 len ,p 指向數(shù)據(jù)的起始地址,len 代表數(shù)據(jù)的字節(jié)大小,將 plen 傳給 DMA,即可直接數(shù)據(jù)發(fā)送了。

image

DMA 發(fā)送完成后,我們還需要更新 LwRB 里的 R 指針,通過 lwrb_skip() 函數(shù)移動 R 指針,如下圖所示:

image

至此,我們就完成了從 LwRB 里零拷貝讀取數(shù)據(jù)的流程,例程可參考:zero_copy_from_lwrb_memory.c 。

向 LwRB 里零拷貝寫入數(shù)據(jù)的操作也類似,相關(guān)接口為:

/* Write data block management */
void*       lwrb_get_linear_block_write_address(LWRB_VOLATILE lwrb_t* buff);
size_t      lwrb_get_linear_block_write_length(LWRB_VOLATILE lwrb_t* buff);
size_t      lwrb_advance(LWRB_VOLATILE lwrb_t* buff, size_t len);

假設(shè)當(dāng)前 LwRB 內(nèi)部狀態(tài)如下圖所示:

image

LwRB 內(nèi)部緩沖區(qū)為空,RW 都處于 4 的位置。

接口 lwrb_get_linear_block_write_address() 可獲取 LwRB 內(nèi)部緩沖區(qū)第一個可寫入字節(jié)的地址,例如上圖中的 W,而 lwrb_get_linear_block_read_length() 函數(shù)返回 LwRB 當(dāng)前可連續(xù)地址寫入的大小,例如上圖中的結(jié)果為 4,表示可連續(xù)寫入 4, 5, 6, 7 處的數(shù)據(jù)。

利用這兩個函數(shù),我們就得到了一片地址連續(xù)的緩沖區(qū),包括其首地址 p 和大小 len,將其傳入 DMA 即可開始接收數(shù)據(jù)了。

DMA 接收完成后,我們還需要更新 LwRB 內(nèi)部的 W 指針,通過 lwrb_advance() 函數(shù)移動 W 指針,如下圖所示:

image

至此,我們就完成了向 LwRB 里零拷貝寫入數(shù)據(jù)的流程,例程可參考:zero_copy_to_lwrb_memory.c 。

線程安全

當(dāng)只有一個寫線程和一個讀線程時,LwRB 是線程安全的,不需要額外加鎖。

詳細(xì)內(nèi)容請參考 LwRB - Thread safety。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容