在 Android 12 中實作 DMABUF 和 GPU 記憶體計算

本頁說明 Android 12 中導入的各項記憶體會計改善項目。

sysfs 中的 DMA-BUF 統計資料

在 Android 11 和 Android 12 中,debugfs 無法在使用者建構版本中掛接。因此,Android 12 的 /sys/kernel/dmabuf/buffers 目錄中新增了 DMA-BUF 統計資料。sysfs

路徑 說明
/sys/kernel/dmabuf/buffers /sys/kernel/dmabuf/buffers 目錄包含每個 DMA-BUF 的內部狀態快照。/sys/kernel/dmabuf/buffers/<inode_number> 包含 DMA-BUF 的統計資料,以及專屬 inode 號碼 <inode_number>
/sys/kernel/dmabuf/buffers/<inode_number>/exporter_name 這個唯讀檔案包含 DMA-BUF 匯出工具的名稱。
/sys/kernel/dmabuf/buffers/<inode_number>/size 這個唯讀檔案會指定 DMA-BUF 的大小 (以位元組為單位)。

libdmabufinfo API 會剖析 DMA-BUF sysfs 統計資料,以顯示每個匯出器和緩衝區的統計資料。

匯出 DMA-BUF 的核心驅動程式必須先叫用 dma_buf_export() API 建立 DMA-BUF,然後再將 struct dma_buf_export_infoexp_name 欄位正確設為匯出器名稱。libdmabufinfodmabuf_dump 工具必須使用這項資訊,才能衍生出每個匯出工具的統計資料,並在錯誤報告中公開。

dmabuf_dump 工具已修改,可透過新引數 -b 輸出這項資訊。

DMA-BUF 堆積架構的統計資料

GKI 2.0 中的 ION 已遭淘汰,請改用 DMA-BUF 堆積架構,這是上游 Linux 核心的一部分。

Android 11 會追蹤下列全域 ION 統計資料:

  • 每個 ION 堆積匯出的 DMA-BUF 總大小
  • 每個 ION 堆積儲存的未使用預先配置記憶體大小總和

Android 11 沒有可用的介面,可顯示每個 ION 堆積的統計資料。

下表比較 ION 統計資料介面,以及 Android 12 中使用 DMA-BUF 堆積架構的裝置對應項目。

Android 11 或搭載 Android 12 的裝置 (支援 ION) 在 Android 12 中推出 DMA-BUF 堆積的裝置
每個堆積的 ION 統計資料 DMA-BUF sysfs 統計資料剖析
匯出的 DMA-BUF 總大小 /sys/kernel/ion/total_heap_size_kb
(不包括非 ION 匯出器匯出的 DMA-BUF 大小)
從 DMA-BUF sysfs 統計資料剖析
(包括匯出的所有 DMA-BUF 大小)。
堆積記憶體總量 /sys/kernel/ion/total_pool_size_kb /sys/kernel/dma_heap/total_pool_size_kb

提高遺失 RAM 的計算準確度

先前計算 RAM 遺失量的方式如下:

最終長度 lostRAM = memInfo.getTotalSizeKb() - (totalPss - totalSwapPss)

- memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()

- kernelUsed - memInfo.getZramTotalSizeKb()

totalPss 元件包含 GPU 記憶體用量 (由 Memtrack HAL 的 getMemory() 介面傳回)。kernelUsed 元件包含 DMA-BUF 記憶體總用量。不過,Android 裝置的 GPU 記憶體來自下列項目:

  • GPU 驅動程式使用實體頁面分配器直接分配的項目
  • 對應至 GPU 位址空間的 DMA-BUF

因此,在計算遺失的 RAM 時,已對應至 GPU 位址空間的 DMA-BUF 會減去兩次。Android 12 實作的解決方案可計算對應至 GPU 位址空間的 DMA-BUF 大小,因此在 Lost RAM 計算中只會計算一次

解決方案詳細資料如下:

  • 以 PID 0 呼叫 Memtrack HAL API getMemory() 時,必須回報 MemtrackType::GLMemtrackRecord::FLAG_SMAPS_UNACCOUNTED 的全域 GPU 私人記憶體總量。
  • 使用 PID 0 呼叫 getMemory() 時,MemtrackType 不得為 GL,且不得失敗。而是必須傳回 0。
  • Android 12 中新增的 GPU 記憶體追蹤點/eBPF 解決方案會計算 GPU 記憶體總量。從 GPU 記憶體總量減去 GPU 私人記憶體總量,即可得出對應至 GPU 位址空間的 DMA-BUF 大小。然後,這個值可用於正確計算 GPU 記憶體用量,進而提高 Lost RAM 計算的準確度。
  • 在大多數 Memtrack HAL 實作中,私有 GPU 記憶體會納入 totalPss,因此必須先重複資料刪除,才能從 lostRAM 中移除。

下一節將詳細說明實作的解決方案。

從遺失的 RAM 中移除 Memtrack 變異性

由於 Memtrack HAL 實作方式可能因合作夥伴而異,因此 HAL 中包含的 GPU 記憶體 totalPSS 不一定一致。為移除 lostRAM 的變異性,系統會在 lostRAM 計算期間,從 totalPss 移除 MemtrackType::GRAPHICSMemtrackType::GL 中佔用的記憶體。

MemtrackType::GRAPHICS 記憶體會從 totalPss 中移除,並替換為 ActivityManagerService.javalostRAM 計算的 totalExportedDmabuf 記憶體,如下列程式碼所示:

final long totalExportedDmabuf = Debug.getDmabufTotalExportedKb();

. . .

final long dmabufUnmapped = totalExportedDmabuf - dmabufMapped;

. . .

// Account unmapped dmabufs as part of the kernel memory allocations
kernelUsed += dmabufUnmapped;

// Replace Memtrack HAL reported Graphics category with mapped dmabufs
totalPss -= totalMemtrackGraphics;
totalPss += dmabufMapped;

MemtrackType::GL 記憶體會從 totalPss 中移除,並在 ActivityManagerService.javalostRAM 計算中,替換為私有 GPU 記憶體 (gpuPrivateUsage),如下列程式碼所示:

final long gpuUsage = Debug.getGpuTotalUsageKb();

. . .

final long gpuPrivateUsage = Debug.getGpuPrivateMemoryKb();

. . .

// Replace the Memtrack HAL-reported GL category with private GPU allocations.
// Count it as part of the kernel memory allocations.
totalPss -= totalMemtrackGl;
kernelUsed += gpuPrivateUsage;

更新遺失 RAM 的計算方式

私有 GPU 記憶體總量和匯出的 DMA 緩衝區記憶體總量都包含在 kernelUsed + totalPss 中,而 kernelUsed + totalPss 會從 lostRAM 中移除。這樣一來,系統就不會重複計算,也不會因 RAM 遺失而導致 Memtrack 變異。

final long lostRAM = memInfo.getTotalSizeKb() - (totalPss - totalSwapPss)
- memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
- kernelUsed - memInfo.getZramTotalSizeKb();

驗證

VTS 測試會強制執行這項規則,凡是搭載 Android 12 且 Linux 核心版本為 5.4 以上的裝置,都必須支援 getGpuDeviceInfo() API。

新的 Memtrack HAL API getGpuDeviceInfo() 必須傳回所用 GPU 裝置的相關資訊。

這有助於改善記憶體會計,並清楚掌握 DMA 緩衝區和 GPU 記憶體用量。導入 memtrack AIDL HAL,改善 RAM 遺失和記憶體會計作業。這項功能不需使用 Google 服務。

實作

這項功能取決於 AIDL Memtrack HAL,且程式碼中包含在 Android 12 中實作這項功能的指示,以註解形式呈現。所有 HIDL HAL 都預計在日後版本中轉換為 AIDL。

下列 API 已新增至 core/java/android/os/Debug.java

   /**
     * Return total memory size in kilobytes for exported DMA-BUFs or -1 if
     * the DMA-BUF sysfs stats at /sys/kernel/dmabuf/buffers could not be read.
     *
     * @hide
     */
    public static native long getDmabufTotalExportedKb();

   /**
     * Return memory size in kilobytes allocated for DMA-BUF heap pools or -1 if
     * /sys/kernel/dma_heap/total_pools_kb could not be read.
     *
     * @hide
     */
    public static native long getDmabufHeapPoolsSizeKb();

如要確認版本是否正常運作,請在 GPU 驅動程式中整合追蹤點,並實作 AIDL memtrack HAL getMemory() API,以便在透過 PID 0 呼叫 MemtrackType::GLMemtrackRecord::FLAG_SMAPS_UNACCOUNTED 時,正確傳回全域 GPU 私人記憶體總計。