Android 12 での DMABUF および GPU メモリ アカウンティングの実装

このページでは、Android 12 で導入されたさまざまなメモリ アカウンティングの改善点について説明します。

sysfs の DMA-BUF 統計情報

Android 11 と Android 12 では、ユーザービルドに debugfs をマウントできません。Android 12 では、/sys/kernel/dmabuf/buffers ディレクトリの sysfs に DMA-BUF 統計情報が追加されています。

パス 説明
/sys/kernel/dmabuf/buffers /sys/kernel/dmabuf/buffers ディレクトリには、すべての DMA-BUF の内部状態のスナップショットが含まれています。 /sys/kernel/dmabuf/buffers/<inode_number> には、一意の inode 番号 <inode_number> を持つ DMA-BUF の統計情報が含まれています。
/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 ヒープ フレームワークの統計情報

アップストリームの Linux カーネルの一部である DMA-BUF ヒープ フレームワークを優先して、GKI 2.0 での ION のサポートが終了します。

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 の損失の計算は次のように行われていました。

final long lostRAM = memInfo.getTotalSizeKb() - (totalPss - totalSwapPss)

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

- kernelUsed - memInfo.getZramTotalSizeKb()

totalPss コンポーネントには、Memtrack HAL の getMemory() インターフェースによって返される GPU メモリの使用量が含まれていました。また、kernelUsed コンポーネントには、DMA-BUF の合計メモリ使用量が含まれていました。しかし、Android デバイスの場合、GPU メモリは次のソースから取得されます。

  • 物理ページ アロケータを使用した GPU ドライバによる直接割り当て
  • GPU アドレス空間にマッピングされた DMA-BUF

そのため、RAM の損失を計算する際に、GPU アドレス空間にメモリマップされた DMA-BUF が 2 回減算されていました。Android 12 では、GPU アドレス空間にマッピングされた DMA-BUF のサイズを計算するソリューションが実装されているため、このサイズは RAM の損失の計算時に 1 回だけ考慮されます。

ソリューションの詳細は次のとおりです。

  • Memtrack HAL API の getMemory() は、PID 0 で呼び出された場合、MemtrackType::GL と MemtrackRecord::FLAG_SMAPS_UNACCOUNTED の全体の GPU プライベート メモリの合計をレポートします。
  • GL 以外の MemtrackType に対して getMemory() を PID 0 で呼び出した場合は失敗せず、代わりに 0 が返されます。
  • Android 12 で追加された GPU メモリ トレースポイント / eBPF ソリューションでは、GPU メモリの合計が考慮されます。合計 GPU メモリから GPU プライベート メモリの合計を差し引くと、GPU アドレス空間にマッピングされた DMA-BUF のサイズがわかります。この値を使用して、GPU メモリの使用量を適切に考慮し、RAM の損失に関する計算の精度を高めることができます。
  • プライベート GPU メモリは、ほとんどの Memtrack HAL 実装で totalPss に含まれているため、lostRAM から削除する前に重複除去する必要があります。

実装されたソリューションについては、次のセクションで詳しく説明します。

RAM の損失から Memtrack のばらつきを取り除く

Memtrack HAL 実装はパートナーごとに異なるため、HAL の totalPSS に含まれる GPU メモリは一貫しているとは限りません。lostRAM からばらつきを取り除くため、MemtrackType::GRAPHICSMemtrackType::GL で考慮されたメモリは、lostRAM の計算中に totalPss から削除されます。

以下に示すように、ActivityManagerService.java における lostRAM の計算では、totalPss から MemtrackType::GRAPHICS メモリが削除され、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;

以下に示すように、ActivityManagerService.java における lostRAM の計算では、totalPss から MemtrackType::GL メモリが削除され、プライベート 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 に含まれていますが、これが lostRAM から削除されます。こうすることで、カウントの重複と Memtrack のばらつきを RAM の損失の計算から排除できます。

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

検証

VTS テストでは、Linux カーネル バージョン 5.4 以降を搭載し、Android 12 でリリースされるデバイスが、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 ドライバに統合したうえで、MemtrackType::GL と MemtrackRecord::FLAG_SMAPS_UNACCOUNTED に対して PID 0 で呼び出されたときに全体の GPU プライベート メモリの合計を正しく返すように AIDL memtrack HAL getMemory() API を実装します。