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

本文介绍了 Android 12 中引入的各种内存计算改进功能。

sysfs 中的 DMA-BUF 统计信息

在 Android 11 和 Android 12 中,debugfs 无法装载到 user build 中。因此,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 的内核驱动程序必须将 struct dma_buf_export_infoexp_name 字段正确设置为导出器名称,然后再通过调用 dma_buf_export() API 创建 DMA-BUF。这对于 libdmabufinfodmabuf_dump 工具得出每个导出器的统计信息(这些信息随后会显示在错误报告中)来说是必需的。

dmabuf_dump 工具已修改为使用新参数 -b 输出这些信息。

DMA-BUF 堆框架的统计信息

GKI 2.0 中的 ION 将被弃用,取而代之的是 DMA-BUF 堆框架,后者是上游 Linux 内核的一部分。

Android 11 会跟踪以下全局 ION 统计信息:

  • 每个 ION 堆导出的 DMA-BUF 的总大小
  • 每个 ION 堆存储的未用预分配内存的总大小

在 Android 11 中,没有任何接口可以用来公开每个 ION 堆的统计信息。

下表比较了 Android 12 中的 ION 统计信息接口与使用 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-BUFF 减去两次。Android 12 实施了一个解决方案,可以计算映射到 GPU 地址空间的 DMA-BUF 的大小,这意味着它在 RAM 损失计算中只计算一次。

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

  • 在使用 PID 0 进行调用时,Memtrack HAL API getMemory() 必须为 MemtrackType::GL 和 MemtrackRecord::FLAG_SMAPS_UNACCOUNTED 报告 GPU 全局专用总内存。
  • 在使用 PID 0MemtrackType(而不是 GL)进行调用时,getMemory() 不得失败,而必须返回 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 中移除。

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(已从 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::GL 和 MemtrackRecord::FLAG_SMAPS_UNACCOUNTED 进行调用时正确返回 GPU 全局专用总内存。