Sysmem VMO 管理

本文档介绍了 Sysmem 用于管理内存的 VMOs 层次结构。本文档假定您熟悉 Fuchsia 上的虚拟内存非 sysmem 内存的统计

所有 VMO 均从堆中分配。当前堆如下所示:

名称 固定池 说明
Sysmem-core 通用主内存
SysmemAmlogicProtectedPool 无法从 CPU 访问内存
SysmemContiguousPool 物理上连续的主内存
tee_secure 专用于 Amlogic 解密编码视频的受保护内存
Sysmem-external-heap(可能有多个) 目前用于金鱼
Sysmem-contig-core 连续主内存;仅在没有 SysmemContiguousPool 的系统上使用

并非所有堆都会从固定池进行子分配;例如,“Core”和“Contig Core”可以从主内存进行分配。某些堆直接与 fuchsia.sysmem.HeapType 值相对应,但所选堆也可能取决于 BufferMemorySettings 成员(例如 is_physically_contiguous)。

Sysmem-core

Sysmem-core 从主内存分配。如果未向内存添加任何约束条件,则此选项为默认选项。

SysmemContiguousPool

系统上的 CPU 和某些其他设备只需要虚拟连续内存。它们可以选择具有任何物理地址的任意页面,并依赖于 MMU 硬件来分配新的连续虚拟地址。这样可以轻松分配内存,因为可以使用任何物理页面。

不过,某些硬件不具备 MMU 或分散收集功能。这种硬件需要物理上连续的地址空间,即内存中的每个页面在 RAM 中的顺序相同。随着系统的运行,主内存会变得越来越碎片化,并且很难找到连续的大量可用内存,因为其他已分配的页面恰好随机分散在内存中。

为了避免出现此问题,Sysmem 有一个单独的连续内存池。它会在启动后不久(内存碎片化之前)分配一个大型内存池,然后将较小的部分分配给应用。从理论上讲,这种内存可能仍然会出现碎片化,但在实践中,由于只有较大的内存块从池中分配,并且一个块中的所有内存都会同时释放回池中,因此这种方法是可行的。

SysmemAmlogicProtectedPool

在搭载 Amlogic SoC 的系统中,需要在具有访问控制权限的特殊区域中分配 DRM(受保护内存)视频,以确保应用无法读取已解密的视频。CPU 必须无法访问这些区域,只有 GPU 和其他硬件才能在特殊模式下访问这些区域,在这种模式下,硬件会通过线路连接,以确保其不会泄露内存。

内存不能被任意标记为受保护或不受保护。硬件只能将少量(小于 32 个)区域标记为受保护区域。为此,sysmem 可以在启动后立即分配一个受保护的池(类似于连续池),并告知固件保护该区域中的所有内存。然后,它可以从此受保护内存池中进行子分配。

tee_secure

tee_secure 适用于存储其他类型数据的另一种受保护内存。必须告知 Zircon 不要从内存中分配,并且永远不要触碰它。ZBI另一个驱动程序可以从固件检索内存及其位置信息,然后告知 sysmem。Sysmem 可以根据需要从此堆中进行子分配。

Sysmem-external-heap

外部堆不一定使用实际内存。例如,Goldfish 堆是一个外部堆,表示 FEMU 虚拟机外部的视频内存。客户端可以传递 VMO 句柄,但不应直接写入内存;而是由 Goldfish 驱动程序使用 VMO koid 查找主机资源。

VMO 层次结构

Sysmem 使用 VMO 层次结构来跟踪客户端的内存用量。有三种方法可以让 VMO 保持活跃状态:

  1. VMO 的句柄。

  2. 将 VMO 映射到进程的地址空间。

  3. 表示已映射到设备的 PMT

出于安全考虑,在所有这些类型的引用都消失之前,sysmem 无法回收 VMO 的内存以供其他客户端使用。对于普通 VMO,内核会通过仅在所有引用都消失后销毁 VMO 来处理此问题。但是,sysmem 会从较大的物理地址范围中进行 VMO 子分配,因此需要了解 VMO 是否已被销毁,以便决定要重复使用哪些内存范围。

内核支持 ZX_VMO_ZERO_CHILDREN 信号,以便处理这些用例:如果 VMO 的所有子项均已关闭,则系统会在父 VMO 上发送 ZX_VMO_ZERO_CHILDREN 信号。

VMO 层次结构

客户端叶 VMO

这些是分发给客户端的 VMO;客户端会在 VMO 分配之前通过调用 BufferCollection.SetName 为它们命名。客户端还可以直接在 VMO 上设置 ZX_PROP_NAME,但不建议这样做,因为 sysmem 驱动程序无法访问该名称。

只要 BufferCollection 继续引用这些 VMO,Sysmem 也会保留对这些 VMO 的引用,即使目前没有任何子项具有 VMO 句柄也是如此。

中间 VMO

每个叶 VMO 都有一个中间 VMO 作为父级。叶级 VMO 和中间 VMO 之间是一对一的映射关系。这些名称由堆设置,对于来自堆的所有 VMO,这些名称通常是固定的。例如,来自连续池的 VMO 的 SysmemContiguousPool-child。

Sysmem 使用这些 VMO 来检测是否已清除对叶 VMO 的所有引用;一旦收到 ZX_VMO_ZERO_CHILDREN 信号,它就会知道可以安全地删除 VMO 并可能重复使用空间。中间 VMO 绝不会传递到系统进程之外,因此客户端绝不能直接引用它们。

堆 VMOs

这些值代表堆中的 VMO 从中分配内存的整个内存池。它们通常在启动后立即分配,以确保有足够的内存可用。堆 VMO 还可以表示划出的物理地址范围,例如 tee_secure 会叠加引导加载程序分配的特定物理范围。

中间 VMO 是从堆 vmo 分配的slice,因此每个中间 VMO 都代表堆中的不同内存范围

如果堆不代表物理内存池,则不需要堆 VMO。在这种情况下,系统会分配中间 VMO,而无需父级 VMO。

报告内存

检查

Sysmem 提供 Inspect 层次结构,以向快照和其他客户端应用报告其内存用量。下面是一个简单的层次结构示例:

  root:
    sysmem:
      collections:
        logical-collection-0:
          allocator_id = 1
          heap = 0
          min_coded_height = 1024
          min_coded_width = 600
          name = vc-framebuffer
          pixel_format = 101
          pixel_format_modifier = 0
          size_bytes = 2490368
          vmo_count = 1
          collection-5:
            channel_koid = 20048
            debug_id = 5498
            debug_name = driver_host
          collection-6:
            channel_koid = 20050
            debug_id = 5498
            debug_name = driver_host
          collection-at-allocation-7:
            debug_id = 19829
            debug_name = virtual-console.cm
            min_buffer_count = 1
          collection-at-allocation-8:
            debug_id = 5498
            debug_name = driver_host
          collection-at-allocation-9:
            debug_id = 5498
            debug_name = driver_host
          vmo-20085:
            koid = 20085
      heaps:
        SysmemContiguousPool:
          allocations_failed = 0
          allocations_failed_fragmentation = 0
          free_at_high_water_mark = 37498880
          high_water_mark = 2490368
          id = 1
          is_ready = true
          last_allocation_failed_timestamp_ns = 0
          max_free_at_high_water = 37498880
          size = 39989248
          used_size = 2490368
          vmo-20085:
            koid = 20085
            size = 2490368
        SysmemRamMemoryAllocator:
          id = 0

Sysmem 会通过 /dev/diagnostics/class/sysmem/XXX.inspect 文件中的检查层次结构报告其对内存的看法(其中 XXX 是伪随机 3 位数字标识符)。所显示的每个逻辑集合都代表一组由一组客户端分配的相同缓冲区。这些逻辑集合包含该集合中实时中间 VMO 的 koid 列表。koid 在系统生命周期内是唯一的,可用于在 memgraph 输出中唯一标识 sysmem VMO。

所有堆也都有检查节点。这些信息可能包括所有子 VMO 的大小和 koid,以及有关堆已满程度和是否失败分配的信息。某些堆仅包含名称和 ID 属性,而没有从中分配的 VMO 的相关信息。

逻辑集合的 allocator_id 与用于分配其内存的堆的 id 相匹配。

由于 sysmem 无法查看系统中的其他进程,因此可检查的数据有限。例如,它不知道哪些其他进程持有对其 VMO 的引用,只知道至少有一个进程持有。它也不知道创建 VMO 的客户端进程的确切名称。Sysmem 客户端应使用其进程名称和 koid 调用 Allocator.SetDebugClientInfo,但系统不会强制执行此操作,也无法保证客户端设置的名称是否正确。

不过,有些信息只能通过检查数据来确定。例如,客户端进程可以保留对 BufferCollection 的通道,而无需保留对 VMO 的任何句柄。只有 sysmem 知道其进程中 BufferCollection 通道和 VMO 之间的映射。channel_koid 属性提供有关频道服务器 koid 的信息。

ZX_INFO_PROCESS_VMOs

memgraphmem 工具使用此系统调用。它可以确定哪些进程有对 VMO 的引用,这对于以安全的方式将内存归因于进程至关重要。

sysmem 使用的 VMO 层次结构可能会导致这些工具出现问题。例如,mem 会忽略没有任何提交内存(有物理内存支持的已分配内存)的 VMO,以免输出内容过于繁杂。这会导致 mem 忽略叶 VMO,因为树中的根 VMO 实际上分配了内存。Mem 有一些黑客技巧,可将内存信息沿着树向下传播到 SysmemContiguousPoolSysmemAmlogicProtectedPool 的子 VMO,它会查看叶 VMO 的“大小”,并假定所有这些内存都已分配。这仅适用于分配时不会重叠的固定大小的池,因此它仅限于一组硬编码的池。

外部堆 VMO 也很复杂,因为它们实际上并不占用客机虚拟机中的内存。因此,mem 不报告它们(它们的已提交内存大小为 0)是正确的做法,但这意味着很难将主机系统上的内存归因于客户机中的进程。

memgraph -v 会对内存信息进行较少的处理,但用户需要自行进行处理以确定内存用量。此外,由于 VMO 不一定具有一致的名称,因此也可能很难确定哪些 VMO 来自 sysmem。

统一方法

任何希望全面准确地了解 sysmem VMO 的实用程序都必须合成 inspect 和 ZX_INFO_PROCESS_VMOS 信息。Sysmem 的检查数据应是哪些 sysmem VMO 存在的可信来源,而内核是哪些进程持有 VMO 引用的可信来源。这需要迭代逻辑缓冲区集合条目并列出其 koid,然后查看 ZX_INFO_PROCESS_VMOS 以查找其大小以及引用其子项的进程。

实用程序可以为每个进程创建 ZX_INFO_HANDLE_TABLE 快照。然后,它可以使用该表在 channel_koid 中查找 koid,以确定哪个进程保留了该 BufferCollection。

在某些情况下,系统无法正确计算内存用量。主要问题是,通道消息中持有的句柄未在任何位置报告,因此无法对这些引用进行说明。客户端可以将 VMO 句柄推入到通道中,而从不从通道中读取,甚至内核也不知道将内存归因于谁。在这些情况下,可以将调试客户端信息用作后备。

未来可能发生的变化

  • 为每个客户端创建一个中间 VMO,以便 sysmem 自行确定哪些客户端仍有对 VMO 的引用。

  • 让组件框架将不可伪造的标识符传递给 sysmem,而不是让客户端传递可伪造的调试名称。