在 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 Common Kernel 的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
如何進行未緩存的系統堆分配來說明這些更改。
分配類型 | 利比昂 | libdmabufheap |
---|---|---|
從系統堆緩存分配 | 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
,因此您可以將其用作參考實現。
所需的未事件添加
對於創建的任何新的特定於設備的 DMA-BUF 堆,將新條目添加到設備的ueventd.rc
文件中。此Setup ueventd to support DMA-BUF heaps 示例演示瞭如何為 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 Common Kernel,它必須使用不同的 ABI 以避免與升級設備發生衝突。
用於 DMA-BUF 堆的編解碼器 2 分配器
用於 DMA-BUF 堆接口的編解碼器 2 分配器在 AOSP 中可用。
允許從 C2 HAL 指定堆參數的組件存儲接口可用於 C2 DMA-BUF 堆分配器。
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 heap name 參數設置為空來創建從<ION heap mask, flag>
到等效 DMA-BUF 堆名稱的映射,而不是使用帶有 name 和 flag 參數的MapNameToIonHeap()
API。
第 4 步:使用適當的堆名稱將ion_alloc_fd()
調用替換為BufferAllocator::Alloc()
。
分配類型 | 利比昂 | libdmabufheap |
---|---|---|
未設置標誌ION_FLAG_MY_FLAG 的my_heap 分配 | 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()
調用。