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 必须告知 Zircon 不要从内存中分配,也不要轻易触碰它。ZBI 必须告知 Zircon 不要从内存中分配,也不要轻易触碰它。ZBI 必须告知 Zircon 不要从内存中分配,也不要轻易触碰它。ZBI 必须告知 Zircon 不要从内存中分配,也不要轻易触碰它。ZBI 必须告知 Zircon 不要从内存中分配,也不要轻易触碰它。ZBI 必须告知 Zircon 不要从内存中分配,也不要轻易触碰它。ZBI 必须告知 Zircon 不要从内存中分配,也不要轻易触碰它。ZBI 必须告知 Zircon 不要从内存中分配,也不要轻易触碰它。ZBI 必须告知 Zircon 不要从内存中分配,也不要轻易触碰它。ZBI 必须告知 Zircon 不要从内存中分配,也不要轻易触碰它。ZBI 必须告知 Zircon 不要从内存中分配,也不要轻易触碰它。ZBI 必须告知 Zircon 不要从内存中分配,也不要轻易触碰它。ZBI 必须告知 Zircon 不要从内存中分配,也不要轻易触碰它。ZBI 必须告知 Zircon 不要从内存中分配,也不要轻易触碰它。ZBI 必须告知 Zircon 不要从内存中分配,也不要轻易触碰它。另一个驱动程序可以从固件检索内存及其位置信息,然后告知 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 绝不会传递到系统进程之外,因此客户端绝不能直接引用它们。

堆 VMO

这些值代表堆中 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 会忽略没有任何提交内存(有物理内存支持的已分配内存),以免输出内容过于繁杂。这会导致 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,而不是让客户端传递可伪造的调试名称。