RFC-0254:更改写入时复制网页的归因

RFC-0254:更改写入时复制网页的归因
状态已接受
领域
  • 内核
说明

更改 Zircon 公开的 API 和语义以归因写入时复制页面。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2024-08-02
审核日期(年-月-日)2024-07-29

问题陈述

目前,Zircon 在将内存归因于 VMO 时公开的行为是 证明 Starnix 内核存在问题:

首先,Zircon 当前的内存归因行为仅与 在 Zircon 的 VMO 实现中使用广泛共享的锁。此共享锁 在运行多个 Starnix 进程时,会导致某些性能问题。

Starnix 使用 VMO 为每个 MAP_ANONYMOUS Linux 映射提供支持,并克隆所有此类 Linux 进程中的 VMO(在进行 fork() 调用时)。许多 Linux 进程 这会导致许多 VMO 争用一小部分共享锁。当我们 对节省内存的优化进行了原型设计,其中 Starnix 为所有设备使用一个大型 VMO 映射,共享锁会退化为“全局”全部锁定 Starnix VMO 参加了两场比赛。

其次,Starnix 无法模拟 Linux 内存归因的确切行为 Zircon 提供的 API。不区分私享和共享 VMO 中的写入时复制页面,因此也不会公开 任何此类共享网页。Starnix 需要此信息才能准确计算 在各种 /proc 文件系统条目中公开的 USSPSS 值。

我们仅提议更改 Zircon 的内存归因 API 及 可以解决这些问题,同时又能保留现有的 以及它们所依赖的基础架构我们的目的并不是试图解决 在用户空间实体(如组件和运行程序)之间分配内存。周三 也不希望这些更改成为归因时,它们是 一种解决特定性能瓶颈和功能缺口的方法 Starnix 今天要迎战。

摘要

改进 Zircon 的内存归因 API 和行为,以将共享内存考虑在内 写入时复制页面。

利益相关方

教员

neelsa@google.com

审核者

  • adanis@google.com
  • jamesr@google.com
  • etiennej@google.com
  • wez@google.com

已咨询

  • maniscalco@google.com
  • davemoore@google.com

社交化

通过与 Zircon 的利益相关方对话, Starnix 和 Memory 团队。相关人员还查看了一份设计文档 简要说明了此 RFC 中提议的更改。

要求

  1. 内核的内存归因实现不得阻止优化 通过使 VMO 层次结构锁定更精细地控制 Zircon 中的锁争用。
  2. memory_monitor 等工具汇总系统中所有 VMO 的归因数据时, 总容量必须与系统的实际物理内存使用量一致。这是 “总和为一”属性。
  3. Starnix 必须能够模拟 Linux 内存归因 API, 由 Zircon 提供的 API。这包括提供 USSRSSPSS 测量结果和其他测量结果(位于相关 proc 文件系统条目中)。

设计

我们提议的更改会影响 VMO 之间共享 Zircon 属性页面的方式 通过写入时复制克隆。这些是 COW 不公开网页和 COW 共享网页。

我们不建议更改进程之间共享 Zircon 属性页面的方式 内存映射或重复的 VMO 句柄。这些是进程不公开的 进程共享页面。

用户空间负责将进程共享页面归因于一个或多个 过程。Zircon 通常不考虑进程共享 但有一种例外情况。请参阅“向后兼容性”用于 。

共享类型是独立的 - 给定网页可以是 COW 私有、COW 共享、进程私有和进程共享。

Zircon 的现有行为

Zircon 的现有归因行为为每个 VMO 提供单一的衡量方式: VMO 的 COW 私有页面和 COW 共享页面中的归因内存, 网页唯一归因。

  • 它会将 COW 共享页面唯一归因于存活时间最早的 VMO, 引用它们。COW 共享页面有时会在同级 VMO 之间共享,因此 引用某个网页的最旧存在的 VMO 无需是其他网页的父项。
  • 对系统级所有 VMO 求和可测量系统总内存用量。
  • 它不会衡量 VMO 的 USS,因为它可能包含一些 COW 共享的网页。
  • 它也不会测量 VMO 的 RSS, 因为它可能并未涵盖所有通过 COW 共享的网页。
  • 它不会测量 VMO 的 PSS,因为该测量会扩缩每个网页的 获得的功劳将按分享次数计算。

Zircon 的新行为

Zircon 的新归因行为提供三种适用于 VMO 的衡量指标:

  • USS:来自 VMO 的 COW 私有页面的归因内存。此测量值 将页面仅归因于该 VMO。它会测量内存使用量 如果 VMO 被销毁,则会释放。
  • RSS:来自 VMO 的 COW 私有页面和 COW 共享页面的归因内存。这个 Measurement 会将 COW 共享页面归因于共享它们的每个 VMO,因此它 多次计数 COW 共享页。它测量的是 VMO 计算的总内存量, 参考。
  • PSS:来自 VMO 的 COW 私有页面和 COW 共享页面的归因内存。这个 衡量机制会在所有 VMO 之间平均分配每个 COW 共享网页的归因数据 共享该页面。对于给定的 VMO,它可以是小数字节, 因为每个 VMO 将获得其共享页面的相同比例字节。它 测量 VMO 的“按比例”内存用量。对所有 VMO 进行求和 系统级容量会衡量总系统内存用量。

我们选择更改归因 API,即公开所有 USSRSSPSS,因为这是我们探索过的备选方案中 满足所有要求。

对用户空间的影响

此提案将继续将“总和为一”现有媒体资源 生成式 AI 进程。归因查询可能会返回不同的结果, 新行为,但汇总系统中所有 VMO 的查询将继续 以提供准确的内存用量计数。这些更改不会导致 例如 memory_monitorps,以开始对内存进行超量计数或计数不足, 实际上将使每个 VMO 和每个任务的计数更加准确。

新 API 公开了 PSS 的小数字节值,用户空间必须选择启用 来观察这些小数字节_fractional_scaled 字段包含 部分字节和用户空间会略微少算 PSS(除非使用它们)。

目前,Starnix 低估了 Linux 映射的 RSS 值,因为 目前的归因行为只计算 COW 共享网页一次。RSS 应该 对共享页面进行计数。Starnix 无法模拟此行为,除非 因此我们更改了 Zircon 归因 API,以提供正确的 RSS 值。我们可以通过再进行一轮 API 更改来达到此目的, 我们选择批量进行更改。

memory_monitor 将 VMO 视为与引用任何 任何子项,甚至对于不直接引用该 VMO 的进程也是如此。 它会在这些进程之间均分父级 VMO 的归因内存。 这会尝试考虑 COW 共享网页,并允许 memory_monitor 但可能会导致一些错误的结果。例如,它可以将 父级 VMO 的 COW 私有页面的一部分,用于不引用 VMO,因此无法引用这些页面也会错误地扩缩父级 COW 共享的网页(如果这些网页已与部分儿童分享,而不与其他儿童分享)。我们的 更改会使此行为变得多余的,我们将将其删除。该工具仍会 当多个进程直接引用 VMO 时,扩缩其内存量。请参阅 "向后兼容性"了解详情。

Syscall API 变更

Zircon 的归因 API 包含 zx_object_get_info 个主题, 更改:

  • ZX_INFO_VMO
    • 我们将通过以下方式保留 ABI 和 API 的向后兼容性: <ph type="x-smartling-placeholder">
        </ph>
      • ZX_INFO_VMO 重命名为 ZX_INFO_VMO_V3,同时保留其值。
      • zx_info_vmo_t 重命名为 ->zx_info_vmo_v3_t
    • 我们将添加新的 ZX_INFO_VMOzx_info_vmo_t,并更改含义 所有 zx_info_vmo_t 版本中两个现有字段的结果:
typedef struct zx_info_vmo {
  // Existing and unchanged `zx_info_vmo_t` fields omitted for brevity.

  // These fields already exist but change meaning.
  //
  // These fields include both private and shared pages accessible by a VMO.
  // This is the RSS for the VMO.
  //
  // Prior versions of these fields assigned any copy-on-write pages shared by
  // multiple VMO clones to only one of the VMOs, making the fields inadequate
  // for computing RSS. If 2 VMO clones shared a single page then queries on
  // those VMOs would count either `zx_system_get_page_size()` or 0 bytes in
  // these fields depending on the VMO queried.
  //
  // These fields now include all copy-on-write pages accessible by a VMO. In
  // the above example queries on both VMO's count `zx_system_get_page_size()`
  // bytes in these fields. Queries on related VMO clones will count any shared
  // copy-on-write pages multiple times.
  //
  // In all other respects these fields are the same as before.
  uint64_t committed_bytes;
  uint64_t populated_bytes;

  // These are new fields.
  //
  // These fields include only private pages accessible by a VMO and not by any
  // related clones. This is the USS for the VMO.
  //
  // These fields are defined iff `committed_bytes` and `populated_bytes` are,
  // and they are the same re: the definition of committed vs. populated.
  uint64_t committed_private_bytes;
  uint64_t populated_private_bytes;

  // These are new fields.
  //
  // These fields include both private and shared copy-on-write page that a VMO
  // can access, with each shared page's contribution scaled by how many VMOs
  // can access that page. This is the PSS for the VMO.
  //
  // The PSS values may contain fractional bytes, which are included in the
  // "fractional_" fields. These fields are fixed-point counters with 63-bits
  // of precision, where 0x800... represents a full byte. Users may accumulate
  // these fractional bytes and count a full byte when the sum is 0x800... or
  // greater.
  //
  // These fields are defined iff `committed_bytes` and `populated_bytes` are,
  // and they are the same re: the definition of committed vs. populated.
  uint64_t committed_scaled_bytes;
  uint64_t populated_scaled_bytes;
  uint64_t committed_fractional_scaled_bytes;
  uint64_t populated_fractional_scaled_bytes;
} zx_info_vmo_t;
  • ZX_INFO_PROCESS_VMOS
    • 我们将通过以下方式保留 ABI 和 API 的向后兼容性: <ph type="x-smartling-placeholder">
        </ph>
      • ZX_INFO_PROCESS_VMOS 重命名为 ZX_INFO_PROCESS_VMOS_V3,同时 以保持其价值
    • 我们将添加新的 ZX_INFO_PROCESS_VMOS
      • 本主题重复使用了 zx_info_vmo_t 结构体系列,因此相关更改 它们在此均适用
  • ZX_INFO_PROCESS_MAPS
    • 我们将通过以下方式保持 ABI 向后兼容性: <ph type="x-smartling-placeholder">
        </ph>
      • ZX_INFO_PROCESS_MAPS 重命名为 ZX_INFO_PROCESS_MAPS_V2,同时 以保持其价值
      • zx_info_maps_t 重命名为 ->zx_info_maps_v2_t
      • zx_info_maps_mapping_t 重命名为 ->zx_info_maps_mapping_v2_t
    • 我们会在一个情况下破坏 API 兼容性: <ph type="x-smartling-placeholder">
        </ph>
      • 重命名 committed_pagespopulated_pages 是一项重大更改。 请参阅“向后兼容性”。
    • 我们将添加新的 ZX_INFO_PROCESS_MAPSzx_info_maps_tzx_info_maps_mapping_t,然后更改现有中两个字段的含义 zx_info_maps_mapping_t 版本。这些字段不会出现在新的 版本(见下文):
typedef struct zx_info_maps {
  // No changes, but is required to reference the new `zx_info_maps_mapping_t`.
} zx_info_maps_t;

typedef struct zx_info_maps_mapping {
  // Existing and unchanged `zx_info_maps_mapping_t` fields omitted for brevity.

  // These fields are present in older versions of `zx_info_maps_mapping_t` but
  // not this new version. In the older versions they change meaning.
  //
  // See `committed_bytes` and `populated_bytes` in the `zx_info_vmo_t` struct.
  // These fields change meaning in the same way.
  uint64_t committed_pages;
  uint64_t populated_pages;

  // These are new fields which replace `committed_pages` and `populated_pages`
  // in this new version of `zx_info_maps_mapping_t`.
  //
  // These fields are defined in the same way as the ones in `zx_info_vmo_t`.
  uint64_t committed_bytes;
  uint64_t populated_bytes;

  // These are new fields.
  //
  // These fields are defined in the same way as the ones in `zx_info_vmo_t`.
  uint64_t committed_private_bytes;
  uint64_t populated_private_bytes;
  uint64_t committed_scaled_bytes;
  uint64_t populated_scaled_bytes;
  uint64_t committed_fractional_scaled_bytes;
  uint64_t populated_fractional_scaled_bytes;
} zx_info_maps_mapping_t;
  • ZX_INFO_TASK_STATS
    • 我们将通过以下方式保留 ABI 和 API 的向后兼容性: <ph type="x-smartling-placeholder">
        </ph>
      • 已将 ZX_INFO_TASK_STATS 重命名为 ZX_INFO_TASK_STATS_V1,同时保留 。
      • zx_info_task_stats_t 重命名为 ->zx_info_task_stats_v1_t
    • 我们将添加新的 ZX_INFO_TASK_STATSzx_info_task_stats_t,以及 更改所有版本中三个现有字段的含义 zx_info_task_stats_t 结构体:
typedef struct zx_info_task_stats {
  // These fields already exist but change meaning.
  //
  // These fields include either private or shared pages accessible by mappings
  // in a task.
  // `mem_private_bytes` is the USS for the task.
  // `mem_private_bytes + mem_shared_bytes` is the RSS for the task.
  // `mem_private_bytes + mem_scaled_shared_bytes` is the PSS for the task.
  //
  // Prior versions of these fields only considered pages to be shared when they
  // were mapped into multiple address spaces. They could incorrectly attribute
  // shared copy-on-write pages as "private".
  //
  // They now consider pages to be shared if they are shared via either multiple
  // address space mappings or copy-on-write.
  //
  // `mem_private_bytes` contains only pages which are truly private - only one
  // VMO can access the pages and that VMO is mapped into one address space.
  //
  // `mem_shared_bytes` and `mem_scaled_shared_bytes` contain all shared pages
  // regardless of how they are shared.
  //
  // `mem_scaled_shared_bytes` scales the shared pages it encounters in two
  // steps: first each page is scaled by how many VMOs share that page via
  // copy-on-write, then each page is scaled by how many address spaces map the
  // VMO in the mapping currently being considered.
  //
  // For example, consider a single page shared between 2 VMOs P and C.
  //
  // If P is mapped into task p1 and C is mapped into tasks p2 and p3:
  // `mem_private_bytes` will be 0 for all 3 tasks.
  // `mem_shared_bytes` will be `zx_system_get_page_size()` for all 3 tasks.
  // `mem_scaled_shared_bytes` will be `zx_system_get_page_size() / 2` for p1
  // and `zx_system_get_page_size() / 4` for both p2 and p3.
  //
  // If P is mapped into task p1 and C is mapped into tasks p1 and p2:
  // `mem_private_bytes` will be 0 for both tasks.
  // `mem_shared_bytes` will be `2 * zx_system_get_page_size()` for p1 and
  // `zx_system_get_page_size()` for p2.
  // `mem_scaled_shared_bytes` will be `3 * zx_system_get_page_size() / 4` for
  // p1 and `zx_system_get_page_size() / 4` for p2.
  uint64_t mem_private_bytes;
  uint64_t mem_shared_bytes;
  uint64_t mem_scaled_shared_bytes;

  // This is a new field.
  //
  // This field is defined in the same way as the "_fractional" fields in
  // `zx_info_vmo_t`.
  uint64_t mem_fractional_scaled_shared_bytes;
} zx_info_task_stats_t;

实现

我们将实现内核 API 结构方面的变更(例如添加和重命名) 字段)作为单个初始 CL 来降低流失率。我们将所有 将新字段设置为 0,但我们要设置的 _fractional_scaled 字段除外 标记值 UINT64_MAX

接下来,我们将更改 memory_monitor 的行为,包括归因父项的 VMO 内存分配给进程,并使用 PSS _scaled_bytes 字段(而不是 RSS _bytes 字段。_fractional_scaled 中的标记值 字段将暂时限制这两种向后不兼容的行为。这个 门控和其他新字段中的值均为 0,这表示用户空间行为 会发生变化

然后,我们将从内核公开新的归因行为。此更改 会改变 committed_bytes 等现有字段的含义, 字段可以采用非零值。_fractional_scaled 字段不能采用 标记值,这样用户空间就会自动开始使用 新行为和新字段,以充分利用这些变更。

最后,我们将取消对 _fractional_scaled 字段的检查,从 memory_monitor

我们更改的旧版 API 被视为已弃用。我们可以移除 我们以后确信没有二进制文件引用它们

性能

新的归因实现方式将提升归因的效果 查询。它处理 VMO 克隆的次数减少了,并避免了成本高昂的检查 用于消除多个 VMO 之间的共享 COW 页面所引发的歧义。

稍后,实现细粒度层次结构锁定将提高 页面错误和多个 VMO 系统调用,包括 zx_vmo_readzx_vmo_writezx_vmo_op_rangezx_vmo_transfer_data。今天,这些行动 所有相关 VMO 共享的层次结构锁,可将所有这些 对相关 VMO 克隆执行的操作。对于 被克隆多次许多 Starnix 和 bootfs VMO 都属于此类别。

在创建和销毁子 VMO 时,Zircon 会执行更多工作。正在创建 改成了 SNAPSHOTSNAPSHOT_MODIFIED 个子项变为 O(#parent_pages)O(1) 个。在任何情况下,销毁任何类型的子项都会变为 O(#parent_pages) 其中,在某些情况下它会变为 O(#child_pages)。目前锆石 将工作转移到更频繁的操作(如页面错误)。将其移至 不频繁的 zx_vmo_create_child()zx_vmo_destroy() 操作 加快其他频繁操作的执行速度我们预计不会出现任何 从而导致用户可见的性能下降。

我们将使用现有的 Microbenchmark 和 Macrobenchmark 来验证预期 效果增量。

向后兼容性

ABI 和 API 兼容性

我们将为所有内核 API 变更创建其他版本化主题, 保留对所有现有主题的支持。这将保持 ABI 兼容性 和 API 兼容性(在所有情况下除外)。

正在重命名 zx_info_maps_mapping_t 中的 committed_pagespopulated_pages 这项更改会破坏 API这些字段仅用于 Fuchsia-internal 工具,因此重命名这些字段的 CL 也会 更改所有调用点的名称。

对于其他更改,无需更新现有的通话网站,除非他们想要 充分利用新添加字段的优势。

行为兼容性

Starnix、ps 以及 Fuchsia 外部的一些库(按 VMO 计算)或 每个任务的 RSS 值。新的归因行为将进行这些计算 因为这样会多次统计 COW 共享网页。这个 会以不向后兼容的方式更改 RSS 值, 更加准确,因此我们预计不会受到任何负面影响。

memory_monitor 为父 VMO 归因内存的方法具有正确性 新的归因行为可以解决的问题。我们必须更改此工具的 以向后不兼容的方式修复问题。为避免 我们将新的 _fractional_scaled 字段设置为标记值 UINT64_MAX,直到我们实现新的归因行为。我们将通过 memory_monitor 对此值的实现。归因绝不会生成 这个标记值是正常的,因此可以作为一个很好的“功能标志”。

我们保留了“共享”的意义,显示为“进程共享”ZX_INFO_TASK_STATS后 尽管它的运作方式与其他归因 API 不同。这样, 依赖于 ZX_INFO_TASK_STATS 行为来继续操作的现有程序 功能正常运行未来工作可能会取代 在 memory_monitor 执行不同查询的 ZX_INFO_TASK_STATS 在此期间,我们可以弃用并移除 ZX_INFO_TASK_STATS

测试

核心测试套件中已包含许多验证内存归因的测试。

我们将添加一些新测试来验证与新归因相关的极端情况 API 和行为。

文档

我们将更新 zx_object_get_info 的系统调用文档以反映这些变更 更改。

我们将在 docs/concepts/memory 下添加一个新页面,用于介绍 Zircon 的 并提供了示例。

缺点、替代方案和未知问题

在开发这种新的解决方案的过程中,我们探索了一些备选解决方案模型。 提案。大多数素材资源很容易实现,但效果却不尽如人意,或者 可维护性特征。

模拟当前归因行为

在此选项中,我们会更改内核以模拟现有归因 新实现的基础。

它没有向后兼容性问题,也不需要更改任何 API。

不过,它会阻止使用精细层次结构锁定,因为它需要 现有共享层次结构锁才能正常运行模拟会导致 归因查询在更精细的锁定策略下发生死锁。它 也会使归因查询的效果更差;他们的表现已经 非常令人担忧最后,该报告并未提供 Starnix 所需的信息 计算 USSRSSPSS 测量值。

仅公开 USS 和 RSS

在此选项中,我们会将内核更改为属性共享写入时复制 向共享它们的每个 VMO 公开 300 个网页,并公开 。

它与细粒度层次结构锁兼容。

但是,这样并不能为用户提供足够的信息来保留 “总和为一”属性或 Starnix 来计算 PSS 测量值。内核 使用此选项会多次属性 COW 共享页面。每个 COW 共享 页的共享次数取决于写入模式, 用户空间将需要每个页面的信息来删除重复的 COW 共享页面,或者 在克隆之间平均分配它们。

公开每个 VMO 树的详细信息

在此选项中,我们会将内核更改为属性共享写入时复制 共享它们的每个 VMO 的页面,并公开每个树和每个 VMO 的信息:

  • 将 VMO 与其树相关联的树标识符
  • 用于决定裁定的固定每 VMO 标识符,例如时间戳
  • 每个 VMO 的私有页面数
  • 每个树的共享页面总数
  • 每个 VMO 可见的每树共享页面数

它可以为用户提供满足“总和为一”所需的信息 属性。

但是,它会阻止我们希望进行的锁争用优化,因为它 需要现有的共享层次结构锁定才能正常运行。这种方法不可行 计算“每个树的共享页总数”没有这种共享锁。这个 选项还会向用户空间公开内核实现详情, 虚拟机代码的后续维护工作将更加困难它也不提供 Starnix 计算 PSS 测量值所需的信息。

先验技术和参考资料

其他操作系统提供用于测试的 RSSUSSPSS 测量值。 但有时使用不同的名称。Windows、MacOS 和 Linux 所有曲目为 RSSUSS。基于 Linux 的操作系统会跟踪 PSS

Linux 通过 proc 文件系统公开这些测量结果。归因工具 例如 ps 和 top 属性,以人性化的方式呈现它们。