在 Android 12 中,GKI 2.0 將 ION 分配器替換為 DMA-BUF 堆,原因如下:
- 安全性:由於每個 DMA-BUF 堆都是單獨的字元設備,因此可以使用 sepolicy 單獨控制對每個堆的存取。這對 ION 來說是不可能的,因為從任何堆疊進行分配只需要存取
/dev/ion
裝置。 - ABI 穩定性:與 ION 不同,DMA-BUF 堆框架的 IOCTL 介面保證 ABI 穩定,因為它是在上游 Linux 核心中維護的。
- 標準化:DMA-BUF 堆疊框架提供了定義良好的 UAPI。 ION 允許自訂標誌和堆疊 ID,這阻止了開發通用測試框架,因為每個裝置的 ION 實作可能表現不同。
Android 通用核心的android12-5.10
分支於2021 年 3 月 1 日禁用了CONFIG_ION
。
背景
以下是 ION 和 DMA-BUF 堆的簡要比較。
ION 和 DMA-BUF 堆框架之間的相似之處
- ION 和 DMA-BUF 堆疊框架都是基於堆疊的 DMA-BUF 導出器。
- 它們都讓每個堆定義自己的分配器和 DMA-BUF 操作。
- 分配性能相似,因為兩種方案都需要單一 IOCTL 進行分配。
ION 和 DMA-BUF 堆疊框架之間的差異
離子堆 | DMA-BUF 堆 |
---|---|
所有 ION 分配均透過/dev/ion 完成。 | 每個 DMA-BUF 堆都是一個字元設備,位於/dev/dma_heap/<heap_name> 。 |
ION 支援堆私有標誌。 | DMA-BUF 堆不支援堆私有標誌。每種不同類型的分配都是從不同的堆中完成的。例如,快取和未快取的系統堆變體是位於/dev/dma_heap/system 和/dev/dma_heap/system_uncached 單獨堆。 |
需要指定堆 ID/遮罩和標誌進行分配。 | 堆名稱用於分配。 |
以下部分列出了處理 ION 的元件,並描述如何將它們切換到 DMA-BUF 堆疊框架。
將核心驅動程式從 ION 轉換為 DMA-BUF 堆
實作 ION 堆的核心驅動程式
ION 和 DMA-BUF 堆都允許每個堆實現自己的分配器和 DMA-BUF 操作。因此,您可以透過使用一組不同的 API 來註冊堆,從 ION 堆實作切換到 DMA-BUF 堆實作。此表顯示 ION 堆註冊 API 及其等價的 DMA-BUF 堆 API。
離子堆 | DMA-BUF 堆 |
---|---|
void ion_device_add_heap(struct ion_heap *heap) | struct dma_heap *dma_heap_add(const struct dma_heap_export_info *exp_info); |
void ion_device_remove_heap(struct ion_heap *heap) | void dma_heap_put(struct dma_heap *heap); |
DMA-BUF 堆不支援堆私有標誌。因此,必須使用dma_heap_add()
API 單獨註冊堆的每個變體。為了促進代碼共享,建議在同一驅動程式中註冊同一堆的所有變體。此dma-buf: system_heap範例顯示了系統堆的快取和未快取變體的實作。
使用此dma-buf:heaps: 範例範本從頭開始建立 DMA-BUF 堆。
核心驅動程式直接從 ION 堆分配
DMA-BUF 堆框架也為核心用戶端提供分配介面。 DMA-BUF 堆提供的介面採用堆名稱作為輸入,而不是指定堆遮罩和標誌來選擇分配類型。
下面顯示了內核內 ION 分配 API 及其等效的 DMA-BUF 堆分配 API。核心驅動程式可以使用dma_heap_find()
API 來查詢堆是否存在。 API 傳回一個指向struct dma_heap實例的指針,然後可以將其作為參數傳遞給dma_heap_buffer_alloc()
API。
離子堆 | DMA-BUF 堆 |
---|---|
struct dma_buf *ion_alloc(size_t len, unsigned int heap_id_mask, unsigned int flags) | |
使用 DMA-BUF 的核心驅動程式
僅導入 DMA-BUF 的驅動程式不需要進行任何更改,因為從 ION 堆分配的緩衝區的行為與從等效 DMA-BUF 堆分配的緩衝區完全相同。
將 ION 的用戶空間用戶端轉換為 DMA-BUF 堆
為了讓 ION 的使用者空間用戶端能夠輕鬆進行轉換,可以使用名為libdmabufheap
抽象程式庫。 libdmabufheap
支援 DMA-BUF 堆和 ION 堆中的分配。它首先檢查指定名稱的 DMA-BUF 堆是否存在,如果不存在,則回退到等效的 ION 堆(如果存在)。
客戶端應在初始化期間初始化BufferAllocator
對象,而不是/dev/ion using ion_open()
。這是因為透過開啟/dev/ion
和/dev/dma_heap/<heap_name>
建立的檔案描述符由BufferAllocator
物件在內部管理。
若要從libion
切換到libdmabufheap
,請修改客戶端的行為,如下所示:
- 追蹤用於分配的堆名稱,而不是頭 ID/掩碼和堆標誌。
- 將採用堆疊遮罩和標誌參數的
ion_alloc_fd()
API 替換為採用堆疊名稱的BufferAllocator::Alloc()
API。
此表透過顯示libion
和libdmabufheap
如何執行未快取的系統堆分配來說明這些變更。
分配類型 | 利比翁 | libdmabuf堆 |
---|---|---|
從系統堆緩存分配 | ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, ION_FLAG_CACHED, &fd) | allocator->Alloc("system", size) |
來自系統堆的未快取分配 | ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, 0, &fd) | allocator->Alloc("system-uncached", size) |
未緩存的系統堆變體正在等待上游批准,但已經是android12-5.10
分支的一部分。
為了支援升級設備, MapNameToIonHeap()
API 允許將堆名稱對應到 ION 堆參數(堆名稱/遮罩和標誌),以允許這些介面也使用基於名稱的分配。這是一個基於名稱的分配範例。
libdmabufheap
公開的每個 API的文件均可用。該庫還公開了一個頭檔供 C 用戶端使用。
參考 Gralloc 實現
Hikey960 gralloc 實作使用libdmabufheap
,因此您可以將其用作參考實作。
所需的 ueventd 添加
對於建立的任何新的特定於裝置的 DMA-BUF 堆,請將新條目新增至裝置的ueventd.rc
檔案中。此設定 ueventd 支援 DMA-BUF 堆的範例示範如何為 DMA-BUF 系統堆完成此操作。
所需的 sepolicy 添加
新增 sepolicy 權限以使用戶空間用戶端能夠存取新的 DMA-BUF 堆。此新增所需權限範例顯示了為各種用戶端存取 DMA-BUF 系統堆疊而建立的 sepolicy 權限。
從框架程式碼存取供應商堆
為了確保 Treble 合規性,框架代碼只能從預先批准的供應商堆類別中進行分配。
根據合作夥伴的回饋,Google 確定了必須從框架程式碼存取的兩類供應商堆:
- 基於具有設備或 SoC 特定性能最佳化的系統堆的堆。
- 從受保護的記憶體中分配的堆。
基於系統堆的堆,具有設備或 SoC 特定的性能最佳化
為了支援此用例,可以覆蓋預設 DMA-BUF 堆系統的堆疊實作。
-
CONFIG_DMABUF_HEAPS_SYSTEM
在gki_defconfig
中關閉,以允許它成為供應商模組。 - VTS 合規性測試確保堆存在於
/dev/dma_heap/system
。這些測試還驗證是否可以從使用者空間分配堆,以及是否可以從使用者空間對傳回的檔案描述符 (fd
) 進行記憶體映射 (mmapped)。
上述幾點對於系統堆的未緩存變體也適用,儘管它的存在對於完全 IO 一致的設備來說不是強制性的。
從受保護記憶體分配的堆
安全堆實作必須是特定於供應商的,因為 Android 通用核心不支援通用安全堆實作。
- 將供應商特定的實作註冊為
/dev/dma_heap/system-secure<vendor-suffix>
。 - 這些堆實作是可選的。
- 如果堆存在,VTS 測試確保可以從中進行分配。
- 框架元件可以存取這些堆,以便它們可以透過 Codec2 HAL/非綁定化、同進程 HAL 啟用堆使用。但是,由於實作細節的可變性,通用 Android 框架功能不能依賴它們。如果將來將通用安全堆實作新增至 Android 通用核心中,則它必須使用不同的 ABI 以避免與升級裝置發生衝突。
DMA-BUF 堆的編解碼器 2 分配器
AOSP 中提供了DMA-BUF 堆介面的編解碼器 2 分配器。
C2 DMA-BUF 堆分配器提供了允許從 C2 HAL 指定堆參數的元件儲存介面。
ION 堆的範例轉換流程
為了平滑從 ION 堆到 DMA-BUF 堆的過渡, libdmabufheap
允許一次切換一個堆。以下步驟示範了用於轉換名為my_heap
的非舊版 ION 堆(支援一個標誌ION_FLAG_MY_FLAG
)的建議工作流程。
步驟 1:在 DMA-BUF 架構中建立 ION 堆的等效項。在此範例中,由於 ION 堆my_heap
支援標誌ION_FLAG_MY_FLAG
,因此我們註冊兩個 DMA-BUF 堆:
-
my_heap
行為與禁用ION_FLAG_MY_FLAG
標誌的 ION 堆的行為完全匹配。 -
my_heap_special
行為與啟用了ION_FLAG_MY_FLAG
標誌的 ION 堆的行為完全匹配。
步驟 2:為新的my_heap
和my_heap_special
DMA-BUF 堆建立 ueventd 變更。此時,堆可見為/dev/dma_heap/my_heap
和/dev/dma_heap/my_heap_special
,並具有預期的權限。
步驟 3:對於從my_heap
分配的客戶端,修改其 makefile 以連結到libdmabufheap
。在客戶端初始化期間,實例化BufferAllocator
物件並使用MapNameToIonHeap()
API 將<ION heap name/mask, flag>
組合對應到等效的 DMA-BUF 堆名稱。
例如:
allocator->MapNameToIonHeap("my_heap_special" /* name of DMA-BUF heap */, "my_heap" /* name of the ION heap */, ION_FLAG_MY_FLAG /* ion flags */ )
您可以透過將 ION 堆名稱參數設為空來建立從<ION heap mask, flag>
到等效 DMA-BUF 堆名稱的映射,而不是使用帶有名稱和標誌參數MapNameToIonHeap()
API。
步驟 4:使用適當的堆名稱將ion_alloc_fd()
呼叫替換為BufferAllocator::Alloc()
。
分配類型 | 利比翁 | libdmabuf堆 |
---|---|---|
從my_heap 分配,未設定標誌ION_FLAG_MY_FLAG | ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, 0, &fd) | allocator->Alloc("my_heap", size |
從my_heap 進行分配,並設定了標誌ION_FLAG_MY_FLAG | ion_alloc_fd(ionfd, size, 0, ION_HEAP_MY_HEAP, ION_FLAG_MY_FLAG, &fd) | allocator->Alloc("my_heap_special", size) |
此時,客戶端可以正常工作,但仍從 ION 堆進行分配,因為它沒有打開 DMA-BUF 堆所需的 sepolicy 權限。
步驟 5:建立用戶端存取新 DMA-BUF 堆所需的 sepolicy 權限。客戶端現在已完全準備好從新的 DMA-BUF 堆疊進行分配。
步驟 6:透過檢查logcat驗證分配是否發生在新的 DMA-BUF 堆疊中。
步驟7:禁用核心中的ION堆my_heap
。如果用戶端程式碼不需要支援升級裝置(其核心可能僅支援 ION 堆),您也可以刪除MapNameToIonHeap()
呼叫。