目錄
1 DMA-BUF API使用指南
1.1 資料結構
1.2 外設的dma-buf操作函數
1.3 核心處理器通路dma-buf緩沖區對象
1.4 使用者空間通過mmap直接通路緩沖區
2 共享記憶體:mmap函數實作
3 GstDmaBufAllocator
1 DMA-BUF API使用指南
by JHJ([email protected])
轉載出自:http://blog.csdn.net/crazyjiang
本文将會告訴驅動開發者什麼是dma-buf共享緩沖區接口,如何作為一個生産者及消費者使用共享緩沖區。
任何一個裝置驅動想要使用DMA共享緩沖區,就必須為緩沖區的生産者或者消費者。
如果驅動A想用驅動B建立的緩沖區,那麼我們稱B為生成者,A為消費者。
生産者:
- 實作和管理緩沖區的操作函數[1];
- 允許其他消費者通過dma-buf接口函數共享緩沖區;
- 實作建立緩沖區的細節;
- 決定在什麼儲存設備上申請記憶體;
- 管理scatterlist的遷徙;
消費者:
- 作為一個緩沖區的消費者;
- 無需擔心緩沖區是如何/在哪裡建立的;
- 需要一個可以通路緩沖區scatterlist的機制,将其映射到自己的位址空間,這樣可以讓自己可以通路到記憶體的同塊區域,實作共享記憶體。
1.1 資料結構
dma_buf是核心資料結構,可以了解為生産者對象。
struct dma_buf {
size_t size;
struct file *file;
struct list_head attachments;
const struct dma_buf_ops *ops;
struct mutex lock;
void *priv;
};
其中
size為緩沖區大小
file為指向共享緩沖區的檔案指針
attachments為附着在緩沖區上的裝置(消費者)
ops為綁定在該緩沖區的操作函數
priv為生産者的私有資料
dma_buf_attachment可以了解為是消費者對象。
struct dma_buf_attachment {
struct dma_buf *dmabuf;
struct device *dev;
struct list_head node;
void *priv;
};
其中
dmabuf為該消費者附着的共享緩沖區
dev為裝置資訊
node為連接配接其他消費者的節點
priv為消費者私有資料
這兩個資料結構的關系如下所示。
1.2 外設的dma-buf操作函數
dma_buf共享緩沖區接口的使用具體包括以下步驟:
- 生産者發出通知,其可以共享一塊緩沖區;
- 使用者空間擷取與該共享緩沖區關聯的檔案描述符,将其傳遞給潛在的消費者;
- 每個消費者将其綁定在這個緩沖區上;
- 如果需要,緩沖區使用者向消費者發出通路請求;
- 當使用完緩沖區,消費者通知生産者已經完成DMA傳輸;
- 當消費者不再使用該共享記憶體,可以脫離該緩沖區;
1. 生産者共享緩沖區
消費者發出通知,請求共享一塊緩沖區。
struct dma_buf *
dma_buf_export(void *priv, struct dma_buf_ops *ops, size_t size, int flags)
如果函數調用成功,則會建立一個資料結構dma_buf,傳回其指針。同時還會建立一個匿名檔案綁定在該緩沖區上,是以這個緩沖區可以由其他消費者共享了(實際上此時緩沖區可能并未真正建立,這裡隻是建立了一個抽象的dma_buf)。
2. 使用者空間擷取檔案句柄并傳遞給潛在消費者
使用者程式請求一個檔案描述符(fd),該檔案描述符指向和緩沖區關聯的匿名檔案。使用者程式可以将檔案描述符共享給驅動程式或者使用者程序程式。
int
dma_buf_fd(struct dma_buf *dmabuf)
該函數建立為匿名檔案建立一個檔案描述符,傳回"fd"或者錯誤。
3. 消費者将其綁定在緩沖區上
現在每個消費者可以通過檔案描述符fd擷取共享緩沖區的引用。
struct dma_buf *
dma_buf_get(int fd)
該函數傳回一個dma_buf的引用,同時增加它的refcount(該值記錄着dma_buf被多少消費者引用)。
擷取緩沖區應用後,消費者需要将它的裝置附着在該緩沖區上,這樣可以讓生産者知道裝置的尋址限制。
struct dma_buf_attachment *
dma_buf_attach(struct dma_buf *dmabuf, struct device *dev)
該函數傳回一個attachment的資料結構,該結構會用于scatterlist的操作。
dma-buf共享架構有一個記錄位圖,用于管理附着在該共享緩沖區上的消費者。
到這步為止,生産者可以選擇不在實際的儲存設備上配置設定該緩沖區,而是等待第一個消費者申請共享記憶體。
4. 如果需要,消費者發出通路該緩沖區的請求
當消費者想要使用共享記憶體進行DMA操作,那麼它就會通過接口dma_buf_map_attachment來通路緩沖區。在調用map_dma_buf前至少有一個消費者與之關聯。
struct sg_table *
dma_buf_map_attachment(struct dma_buf_attachment *, enum dma_data_direction);
該函數是dma_buf->ops->map_dma_buf的一個封裝,它可以對使用該接口的對象隐藏"dma_buf->ops->"
struct sg_table *
(*map_dma_buf)(struct dma_buf_attachment *, enum dma_data_direction);
生産者必須實作該函數。它傳回一個映射到調用者位址空間的sg_table,該資料結構包含了緩沖區的scatterlist。
如果第一次調用該函數,生産者現在可以掃描附着在共享緩沖區上的消費者,核實附着裝置的請求,為緩沖區選擇一個合适的實體存儲空間。
基于枚舉類型dma_data_direction,多個消費者可能同時通路共享記憶體(比如讀操作)。
如果被一個信号中斷,map_dma_buf()可能傳回-EINTR。
5. 當使用完成,消費者通知生成者DMA傳輸結束
當消費者完成DMA操作,它可以通過接口函數dma_buf_unmap_attachment發送“end-of-DMA”給生産者。
void
dma_buf_unmap_attachment(struct dma_buf_attachment *, struct sg_table *);
該函數是dma_buf->ops->unmap_dma_buf()的封裝,對使用該接口的對象隐藏"dma_buf->ops->"。
在dma_buf_ops結構中,unmap_dma_buf定義成
void
(*unmap_dma_buf)(struct dma_buf_attachment *, struct sg_table *);
unmap_dma_buf意味着消費者結束了DMA操作。生産者必須要實作該函數。
6. 當消費者不再使用該共享記憶體,則脫離該緩沖區;
當消費者對該共享緩沖區沒有任何興趣後,它應該斷開和該緩沖區的連接配接。
a. 首先将其從緩沖區中分離出來。
void
dma_buf_detach(struct dma_buf *dmabuf, struct dma_buf_attachment *dmabuf_attach);
此函數從dmabuf的attachment連結清單中移除了該對象,如果消費者實作了dma_buf->ops->detach(),那麼它會調用該函數。
b. 然後消費者傳回緩沖區的引用給生産者。
void
dma_buf_put(struct dma_buf *dmabuf);
該函數減小緩沖區的refcount。
如果調用該函數後refcount變成0,該檔案描述符的"release"函數将會被調用。它會調用dmabuf->ops->release(),企圖釋放生産者為dmabuf申請的記憶體。
注意事項:
a. attach-detach及{map,unmap}_dma_buf成對執行非常重要。
attach-detach函數調用可以讓生産者明确目前消費者對實體記憶體的限制。如果可能,它會在不同的儲存設備上申請或/和移動實體頁框。
b. 如果有必要,需要将緩沖區移動到另一個實體位址空間。
如果
- 至少有一個map_dma_buf存在,
- 該緩沖區已經配置設定了實體記憶體,
此時另一個消費者打算使用該緩沖區,生産者可能允許其請求。
如果生産者允許其請求:
如果新的消費者有嚴格的DMA尋址限制,而且生産者可以處理這些限制,那麼生産者會在map_dma_buf裡等待剩餘消費者完成緩沖區通路。一旦所有消費者都完成了通路并且unmap了緩沖區,生産者可以将該緩沖區轉移到嚴格的實體位址空間,然後再次允許{map,unmap}_dma_buf操作移動後的共享緩沖區。
如果生産者不能滿足新消費者的尋址限制,調用dma_buf_attach() 則會傳回失敗。
1.3 核心處理器通路dma-buf緩沖區對象
允許處理器在核心空間作為一個消費者通路dma-buf對象的原因如下:
- 撤銷/回退操作。比如一個裝置連接配接到USB總線上,在發送資料前核心需要将第一個資料移除。
- 對其他消費者而言這個是全透明的。比如其他使用者空間消費者注意不到一個 dma-buf是否做過一次撤銷/回退操作。
在核心上下文通路dma_buf需要下面三個步驟:
1. 通路前的準備工作,包括使相關cache無效,使處理器可以通路緩沖區對象;
2. 通過dma_buf map接口函數以頁為機關通路對象;
3. 完成通路時,需要重新整理必要的處理器cache,釋放占用的資源;
1. 通路前的準備工作
處理器在核心空間打算通路dma_buf對象前,需要通知生産者。
int
dma_buf_begin_cpu_access(struct dma_buf *dmabuf, size_t start, size_t len,
enum dma_data_direction direction)
生産者可以確定處理器可以通路這些記憶體緩沖區,生産者也需要确定處理器在指定區域及指定方向的通路是一緻性的。生産者可以使用通路區域及通路方向來優化cache flushing。比如通路指定範圍外的區域或者不同的方向(用讀操作替換寫操作)會導緻陳舊的或者不正确的資料(比如生産者需要将資料拷貝到零時緩沖區)。
該函數調用可能會失敗,比如在OOM(記憶體緊缺)的情況下。
2. 通路緩沖區
為了支援處理器可以通路到駐留在高端記憶體中的dma_buf對象,需要調用一個和kmap類似的接口函數。通路dma_buf需要頁對齊。在通路對象前需要先做映射工作,及需要得到一個核心虛拟位址。操作完後,需要取消該對象的映射。
void *
dma_buf_kmap(struct dma_buf *, unsigned long);
void
dma_buf_kunmap(struct dma_buf *, unsigned long, void *);
該函數有對應的原子操作函數,如下所示。在調用原子操作函數時,生産者和消費者都不能被阻塞。
void *
dma_buf_kmap_atomic(struct dma_buf *, unsigned long);
void
dma_buf_kunmap_atomic(struct dma_buf *, unsigned long, void *);
生産者在同一時間不能同時調用原子操作函數(在任何程序空間)。
如果通路緩沖區區域不是頁對齊的,雖然kmap對應的區域資料得到了更新,但是在這個區域附近的區域資料也相應得到了更新,這個不是我們所希望的。也就是說kmap更新了自己關心的區域外,還更新了其他區域,對于那些區域的使用者來說,資料就已經失效了。
下圖給出了一個例子,一共有四個連續的頁,其中kmap沒有頁對齊擷取部分緩沖區,即紅色部分,由于會同步cache,其附近的區域資料也會被更新,被更新區域的範圍和cache行的大小有關系。
注意這些調用總是成功的,生産者需要在begin_cpu_access中完成所有的準備,在這其中可能才會有失敗。
3. 完成通路
當消費者完成對begin_cpu_access指定範圍内的緩沖區通路,需要通知生産者(重新整理cache,同步資料集釋放資源)。
void dma_buf_end_cpu_access(struct dma_buf *dma_buf,
size_t start, size_t len,
enum dma_data_direction dir);
1.4 使用者空間通過mmap直接通路緩沖區
在使用者空間映射一個dma-buf對象,主要有兩個原因:
- 處理器回退/撤銷操作;
- 支援消費者程式中已經存在的mmap接口;
1. 處理器在一個pipeline中回退/撤銷操作
在處理pipeline過程中,有時處理器需要通路dma-buf中的資料(比如建立thumbnail, snapshots等等)。使用者空間程式通過使用dma-buf的檔案描述符fd調用mmap來通路dma-buf中的資料是一個好辦法,這樣可以避免使用者空間程式對共享記憶體做一些特殊處理。
進一步說Android的ION架構已經實作了該功能(從使用者空間消費者來說它實作了一個和dma-buf很像的東西,使用fds用作檔案句柄)。是以實作該功能對于Android使用者空間來說是有意義的。
沒有特别的接口,使用者程式可以直接基于dma-buf的fd調用mmp。
2. 支援消費者程式中已經存在的mmap接口
與處理器在核心空間通路dma-buf對象目的一樣,使用者空間消費者可以将生産者的dma-buf緩沖區對象當做本地緩沖區對象一樣使用。這對drm特别重要,其Opengl,X的使用者空間及驅動代碼非常巨大,重寫這部分代碼讓他們用其他方式的mmap,工作量會很大。
int
dma_buf_mmap(struct dma_buf *, struct vm_area_struct *, unsigned long);
參考文獻
[1] struct dma_buf_ops in include/linux/dma-buf.h
[2] All interfaces mentioned above defined in include/linux/dma-buf.h
[3] https://lwn.net/Articles/236486/
[4] Documentation/dma-buf-sharing.txt
2 共享記憶體:mmap函數實作
https://blog.csdn.net/callinglove/article/details/46710465
相關API
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
void *mmap64(void *addr, size_t length, int prot, int flags,
int fd, off64_t offset);
int munmap(void *addr, size_t length);
int msync(void *addr, size_t length, int flags);
mmap函數說明:
1. 參數 addr 指明檔案描述字fd指定的檔案在程序位址空間内的映射區的開始位址,必須是頁面對齊的位址,通常設為 NULL,讓核心去選擇開始位址。任何情況下,mmap 的傳回值為記憶體映射區的開始位址。
2. 參數 length 指明檔案需要被映射的位元組長度。off 指明檔案的偏移量。通常 off 設為 0 。
- 如果 len 不是頁面的倍數,它将被擴大為頁面的倍數。擴充的部分通常被系統置為 0 ,而且對其修改并不影響到檔案。
- off 同樣必須是頁面的倍數。通過 sysconf(_SC_PAGE_SIZE) 可以獲得頁面的大小。
3. 參數 prot 指明映射區的保護權限。通常有以下 4 種。通常是 PROT_READ | PROT_WRITE 。
- PROT_READ 可讀
- PROT_WRITE 可寫
- PROT_EXEC 可執行
- PROT_NONE 不能被通路
4. 參數 flag 指明映射區的屬性。取值有以下幾種。MAP_PRIVATE 與 MAP_SHARED 必選其一,MAP_FIXED 為可選項。
- MAP_PRIVATE 指明對映射區資料的修改不會影響到真正的檔案。
- MAP_SHARED 指明對映射區資料的修改,多個共享該映射區的程序都可以看見,而且會反映到實際的檔案。
- MAP_FIXED 要求 mmap 的傳回值必須等于 addr 。如果不指定 MAP_FIXED 并且 addr 不為 NULL ,則對 addr 的處理取決于具體實作。考慮到可移植性,addr 通常設為 NULL ,不指定 MAP_FIXED。
5. 當 mmap 成功傳回時,fd 就可以關閉,這并不影響建立的映射區。
3 GstDmaBufAllocator
GstDmaBufAllocator — Memory wrapper for Linux dmabuf memory
https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-libs/html/gst-plugins-base-libs-dmabuf.html
#include <gst/allocators/gstdmabuf.h>