在 Android 12 中实现 DMABUF 和 GPU 内存核算

本页面介绍了 Android 12 中引入的各种内存核算改进。

sysfs 中的 DMA-BUF 统计信息

在 Android 11 和 Android 12 中,debugfs 无法在用户版本中挂载。因此,DMA-BUF 统计信息已添加到 Android 12 的 /sys/kernel/dmabuf/buffers 目录中的 sysfs 中。

路径 说明
/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 堆框架的统计信息

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 的计算方式如下

final long 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 的大小,这意味着它在丢失 RAM 计算中被计算一次。

该解决方案的详细信息如下

  • 当使用 PID 0 调用 Memtrack HAL API getMemory() 时,必须针对 MemtrackType::GL 和 MemtrackRecord::FLAG_SMAPS_UNACCOUNTED 报告全局总 GPU 私有内存。
  • 当为 PID 0 调用 getMemory()MemtrackType 不是 GL 时,不得失败。而是必须返回 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 中的可变性,在 lostRAM 计算期间,将 MemtrackType::GRAPHICSMemtrackType::GL 中计算的内存从 totalPss 中移除。

MemtrackType::GRAPHICS 内存从 totalPss 中移除,并在 ActivityManagerService.java 中的 lostRAM 计算中替换为 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.java 中的 lostRAM 计算中替换为私有 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 缓冲区内存总量都包含在从 lostRAM 中移除的 kernelUsed + totalPss 中。这消除了丢失 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,以便在为 MemtrackType::GL 和 MemtrackRecord::FLAG_SMAPS_UNACCOUNTED 使用 PID 0 调用时正确返回全局总 GPU 私有内存。