天天看點

linux之DMA-BUF使用指南1 DMA-BUF API使用指南2 共享記憶體:mmap函數實作3 GstDmaBufAllocator

目錄

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共享緩沖區接口的使用具體包括以下步驟:

  1. 生産者發出通知,其可以共享一塊緩沖區;
  2. 使用者空間擷取與該共享緩沖區關聯的檔案描述符,将其傳遞給潛在的消費者;
  3. 每個消費者将其綁定在這個緩沖區上;
  4. 如果需要,緩沖區使用者向消費者發出通路請求;
  5. 當使用完緩沖區,消費者通知生産者已經完成DMA傳輸;
  6. 當消費者不再使用該共享記憶體,可以脫離該緩沖區;

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>
           

繼續閱讀