RFC-0226:Zircon 分页器回写

RFC-0226:Zircon 寻呼机回写
状态已接受
领域
  • 内核
说明

内核支持跟踪和回写对分页器支持的 VMO 所做的修改

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2023-04-13
审核日期(年-月-日)2023-09-19

摘要

本文档介绍了 Zircon 内核对分页器支持内存的支持, 修改,然后写回(同步)到分页器源,例如存储空间 磁盘。

设计初衷

Zircon 支持创建由 用户空间分页器服务,通常由文件系统托管。单个文件 在内存中以 VMO 表示当 VMO 的页面被访问时,它们会 按需触发故障,而用户寻呼机读取网页的内容来自 磁盘。

Zircon pager API 最初仅支持只读文件;没有 被写入内存中被脏污染的 VMO 页面被写回的机制, 磁盘。这种设计足以托管 blobfs 等不可变文件系统, 提供 Fuchsia 上的所有可执行文件和其他只读文件。不过, 通用文件系统需要回写支持,其中文件 内容可由客户端修改,因此需要同步回磁盘。

如果没有回写支持,minfsfxfs 等可变文件系统便无法实现 充分利用需求分页功能解决方法是,将可变文件系统 使用匿名(非分页器支持的)VMO 将文件缓存在内存中,并管理 这些 VMO 本身的内容。这些 VMO 可能需要 即使这些页面内的某些页面很少或从不 。将这些匿名 VMO 切换为由分页器支持的 VMO,这样网页可以 出现故障并按需逐出,让可变文件系统能够更好地利用 内存。回写支持还允许可变文件系统的客户端直接 对 VMO 执行读写操作,而不是依靠通道获取数据 因为受信道限制的限制,速度可能会极其慢。

在本章的其余部分,“用户分页器”和“文件系统”这两个术语可以互换使用 具体取决于上下文。

利益相关方

教员

  • cpu@google.com

审核者

  • adanis@google.com、csuter@google.com

已咨询

  • brettw@google.com、cdrllrd@google.com、godtamit@google.com、 maniscalco@google.com、travisg@google.com

社交化

此 RFC 已由本地存储团队进行了设计审核。

要求

提议的设计旨在实现以下目标:

  1. 增强 Zircon,以支持对分页器支持的 VMO 进行回写,从而允许构建 高性能可变文件系统(使用 Zircon 流)。
  2. 支持通过虚拟机映射读取和写入文件(考虑使用 mmap 的文件)。
  3. 提供用户分页器尽最大努力清除脏页,以减少 因意外关停而导致数据丢失的风险。
  4. 在未来的迭代中,允许内核(通过用户分页器)逐出脏页 以应对系统内存压力。

以及一些非目标:

  1. 在用户分页器发起的刷新之外,内核不会执行任何 脏页的清理速率尽管如此, 未来的发展可能会增加一项功能, 数据并让内核根据该数据量发起回写请求。
  2. 防止因违反内核/分页器协议而导致数据丢失 非目标。如果用户分页器无法查询脏页并将其写回 然后再将其句柄关闭到 VMO(或终止),那么数据丢失 。

设计

概览

所提议的设计旨在支持两个直接的用例:

  • 文件系统客户端可以通过作为内核对象的流访问文件 封装文件 VMO这大致可以视为类似于 zx_vmo_read()zx_vmo_write(),因为流在内部进行系统调用 封装 VMO 读/写内核例程。
  • 文件系统客户端还可以对文件执行 mmap 操作,这大致相当于 将文件 VMO 映射(使用 zx_vmar_map)到客户端进程地址 空间。

为简单起见,本文档的其余部分将讨论直接交互 文件 VMO,通过系统调用 (zx_vmo_read/write) 或通过虚拟机 映射 (zx_vmar_map)。

下面几个示例展示了哪些互动涉及回写 可能是什么样子

示例 1

  1. 文件系统客户端会对给定范围内的文件 VMO 执行 zx_vmo_read()
  2. 由于 VMO 受分页器支持,因此内核会生成一个读取请求, 关联的用户寻呼机。
  3. 由文件系统托管的用户寻呼机满足了此请求。它提供 包含从磁盘读取的内容的页面。
  4. 文件系统客户端会对 VMO 执行 zx_vmo_write() 操作, 范围。VMO 的页面之前已在第 3 步中填充,因此可以 写入数据。所做的修改目前仅在内存中,但 在某个时间点需要反映在磁盘上
  5. 用户分页器向内核查询 VMO 中已被 脏乱 / 被修改。这可以作为定期背景刷新的一部分来实现 文件系统执行或响应文件系统请求的刷新 客户端。
  6. 用户分页器将查询的脏范围写回磁盘。目前 即修改后的文件内容已成功保存到磁盘

示例 2

  1. 文件系统客户端使用 zx_vmar_map() 映射文件 VMO。映射开始时 位于addr。客户端读取从 addr 开始的范围。
  2. 与示例 1 相同。
  3. 与示例 1 相同。
  4. 文件系统客户端现在会从 addr 开始向同一范围写入数据。通过 基础页面已经填充过,因此内容 在内存中进行了修改。您需要在 2024 年 3 月 8 日之前 在某个时间点
  5. 与示例 1 相同。
  6. 与示例 1 相同。

此处的示例从执行写入之前的 VMO 读取开始。请注意, 这样做只是为了将用户分页器的页面填充分为一个 为清晰起见,这是单独的步骤。客户端可以直接写入到 记忆功能写入将阻塞,直到用户分页器提供页面 。

上述两个示例均假定文件系统遵循覆盖模型 表示可以直接写入已填充(已提交)的页面,而无需 来请求额外的存储空间修改后的内容将写回原来的 因此无需为修改分配额外的空间。 不过,fxfsminfs 等文件系统使用的是写入时复制 (CoW) 模型, 每次修改都需要在磁盘上分配新空间。所以我们 需要一种机制来为已经写入的页面预留空间 承诺;第 4 步已修改为等待该预留,然后才能执行写入操作 继续。

为了执行回写,Zircon 寻呼机 API 进行了扩展,可支持以下各项:

  • 内核会屏蔽用户分页器指示应写入的 VMO 遵循写时复制方案,并在用户传呼机获得 已确认写入。
  • 内核会跟踪 VMO 中的脏页,并具有一种机制来发现 传递给用户传呼机。
  • 用户分页器在同步 VMO 及其完成时间,以便内核可以更新脏跟踪 状态。
  • 内核还会根据用户分页器显示有关 VMO 大小调整的信息。
  • 用户分页器可以查询内核跟踪的相关信息 代表其 ID,例如上次修改 VMO 的时间。

分页器和 VMO 创建

分页器创建系统调用保持不变,即使用 zx_pager_create() 以创建将 options 设置为 0 的分页器。

由分页器支持的 VMO 使用 zx_pager_create_vmo() 创建, 以及一个密钥(用于该端口的页面请求数据包), VMO。zx_pager_create_vmo() 系统调用还支持新的 options 标志 ZX_VMO_TRAP_DIRTY。这表示内核应捕获对 VMO,并首先从用户分页器请求确认写入。 此标志适用于在“写入时复制”模式下运行的文件。更多 稍后查找关于此标志的详细信息

// Create a VMO (returned via |out|) backed by |pager|. Pager requests will be
// queued on |port| and will contain the provided |key| as an identifier.
// |size| will be rounded up to the page boundary.
//
// |options| must be 0 or a combination of the following flags:
// ZX_VMO_RESIZABLE - if the VMO can be resized.
// ZX_VMO_TRAP_DIRTY - if writes to clean pages in the VMO should be trapped by the
// kernel and forwarded to the pager service for acknowledgement before proceeding
// with the write.
zx_status_t zx_pager_create_vmo(zx_handle_t pager,
                                uint32_t options,
                                zx_handle_t port,
                                uint64_t key,
                                uint64_t size,
                                zx_handle_t* out);

默认情况下,所有由分页器支持的 VMO 都被视为可变;这也适用于 实现只读文件系统,无需支付额外费用。需要转换的代码路径 修改页面假定不适用于只读 VMO。如果 VMO 被修改(可能是意外滥用),但用户传呼机绝不会 会查询其中的脏页并尝试写回它们, 内容只会保留在内存中。将来,当内核 回写请求,用户分页器可以处理此类请求的回写请求, VMO 报告为错误,或者直接忽略它们。

提供 VMO 页面

由分页器支持的 VMO 中的页面由用户分页器按需填充 zx_pager_supply_pages()。此系统调用 已存在,可与只读 VMO 搭配使用。

回写的页面状态

由分页器支持的 VMO 可以让页面处于三种状态:DirtyCleanAwaitingClean。这些状态在 vm_page_t 中进行编码。

三种状态之间的转换遵循以下步骤:

  1. 为新提供 zx_pager_supply_pages() 的网页 名称:Clean
  2. 当页面被写入时,它会转换为 Dirty。如果 VMO 已被 使用 ZX_VMO_TRAP_DIRTY 创建,则内核会在确认 先从用户寻呼机请求 DIRTY 寻呼机请求。更多详情 互动。
  3. 用户分页器通过 系统调用。
  4. 对于内核返回的每个脏页,用户分页器都会调用系统调用 来指示内核开始回写页面 将其状态更改为 AwaitingClean
  5. 如果页面写入到此时间点之后,其状态会切换回 Dirty
  6. 当完成回写页面的操作时,用户寻呼机将发出另一个系统调用。 如果此时页面状态为 AwaitingClean,则会转换为 Clean
  7. 如果用户在回写过程中遇到错误,则该网页会保留 AwaitingClean。日后查询脏页时,系统会同时返回 AwaitingCleanDirty 页,以便用户分页器可以尝试回写相应页 。

下面的状态图展示了这些状态之间的转换。

脏状态转换图

AwaitingClean 需要作为几种不同状态的单独状态进行跟踪 原因:

  • 即使同时处于 CleanAwaitingClean 状态的网页转换为 Dirty(当写入到用户分页器正在处理的页面时) 与 Clean 网页的处理方式不同。Clean 页 能够在内存紧张时回收 需要受到保护,以防被收回。

  • 内核需要知道哪个版本的网页已被写回, 可以在用户分页器完成后将其正确转换为 Clean。这是 请务必区分 flush 之前发生的页面写入( 而后进入的磁盘(需要 )。

  • 我们可以在回写开始时避免来自用户分页器的系统调用, 而内核只需在返回AwaitingClean 作为用户分页器的脏页查询的一部分。但也可能是 刷新网页后,用户分页器 而这会留出较长的时间窗口,以便系统再次清理这些数据。拥有一个 将回写窗口括起来越紧,用户就越有可能 分页器就能够成功将页面变为 Clean 状态。

为了更新脏状态,内核会跟踪将页面写入 包括通过 VMO 和流式写入系统调用,以及通过虚拟机映射。请注意, 这适用于通过虚拟机映射发生的任何写入 也就是说,此政策也适用于内核执行的写入操作 在系统调用中包含 user_copy,例如 zx_channel_read()

在系统调用期间推断脏页(例如 zx_vmo_write())非常简单, 因为指定了范围。访问 VMO 的另一种方式是通过虚拟机 进程中的映射”地址空间为了在 由分页器支持的 VMO,可写映射开始时会移除写入权限 。写入会生成保护 错误,可通过恢复写入权限并将页面标记为 状态为 Dirty

此处提到的 Dirty 状态是由 vm_page_t,即软件跟踪的脏状态。脏位跟踪 x86 上的硬件分页表,但我们不打算使用它 为初始实现派生页面的脏状态。我们需要跟踪 反之,对于不支持脏页的旧版 arm64 平台,软件中的脏位 位。因此,为了保持一致性和简易性 选择在开始时不使用硬件脏位来推断网页的 脏状态 / 干净状态。依赖硬件页面表格位也会造成 页表回收方面的复杂问题,需要考虑到这一点 未来我们确实要依赖硬件位

值得注意的是,只有由分页器直接支持的 VMO 才有资格进入脏页 跟踪。换句话说,由分页器支持的 VMO 的 CoW 克隆不会选择 脏跟踪,并且看不到任何回写请求。符合以下条件的网页: 以副本形式编写的网页是从父网页副本派生而来,而克隆 直接拥有这些页面的所有权。

为待处理的写入操作预留空间

写入标记为使用 ZX_VMO_TRAP_DIRTY 创建选项标志,需要获得 文件系统此解决方案分为两部分提出,其中 v1 先是简单的 并更加注重正确性,而 v2 是在 v1 的基础上构建的, 性能v1 提案主要采用同步模式, 从而为新写入操作预留空间。对于第 2 版, 表示内核中的脏预留配额以及它们如何应用于 VMO,因此 让内核可以自行跟踪预留这将有助于提升 减少内核 和文件系统

ZX_VMO_TRAP_DIRTY v1

ZX_VMO_TRAP_DIRTY VMO 创建标志表示内核应捕获 页面中的任何 Clean->Dirty 页面转换(或 AwaitingClean->Dirty 转换) VMO。当有写入操作进入尚未脏页时,内核会生成 ZX_PAGER_VMO_DIRTY 寻呼请求。对于通过虚拟机映射执行的写入操作,请求 跨越包含错误地址的单个页面。对于流式/VMO 写入, 内核会在每次连续运行非脏页时发送一个请求, 一个范围。

范围 [start, end) 的 dirty 请求如下所示。

zx_packet_page_request_t request {
    .command = ZX_PAGER_VMO_DIRTY,
    .flags = 0,
    // |offset| and |length| will be page-aligned.
    .offset = start,
    .length = end - start,
};

ZX_VMO_TRAP_DIRTY 创建标志适用于在 CoW 中写入的文件 模式;对于稀疏文件,则设为“覆盖”模式。如果未指定该标志 页面被标记为 Dirty,并且无需用户寻呼机参与即可进行写入;这个 适用于以覆盖模式写入的非稀疏文件。

用户寻呼机使用ZX_PAGER_VMO_DIRTY zx_pager_op_range()

  • ZX_PAGER_OP_DIRTY 会将尚未 Dirty 的页面的状态设置为 Dirty, 并且内核继续执行阻塞的写入。
  • ZX_PAGER_OP_FAIL 不会更改网页的当前状态,并且会使 zx_vmo_write() 调用发起了写入,生成严重页面错误 虚拟机映射的例外情况;如果是 zx_stream_write(),则返回 部分写入成功完成。
// |pager| is the pager handle.
// |pager_vmo| is the VMO handle.
// |offset| and |length| specify the range, i.e. [|offset|, |offset| + |length|).
//
// |op| can be:
//
// ZX_PAGER_OP_DIRTY - The userspace pager wants to transition pages in the range
// [offset, offset + length) from clean to dirty. This will unblock any writes that
// were waiting on ZX_PAGER_VMO_DIRTY page requests for the specified range.
// |data| must be 0.
//
// ZX_PAGER_OP_FAIL - The userspace pager failed to fulfill page requests for
// |pager_vmo| in the range [offset, offset + length) with command
// ZX_PAGER_VMO_READ or ZX_PAGER_VMO_DIRTY.
//
// |data| contains the error encountered, a zx_status_t error code sign-extended
// to a |uint64_t| value - permitted values are ZX_ERR_IO, ZX_ERR_IO_DATA_INTEGRITY,
// ZX_ERR_BAD_STATE and ZX_ERR_NO_SPACE.
zx_status_t zx_pager_op_range(zx_handle_t pager,
                              uint32_t op,
                              zx_handle_t pager_vmo,
                              uint64_t offset,
                              uint64_t length,
                              uint64_t data);

取决于文件系统刷新脏数据以及标记页面的频率 Clean,那么在以下情况下,此方法可能会导致性能下降: 由客户端编写为避免此类开销,文件系统可能需要延迟刷新 尽可能多地保留脏数据,这并不是提供 脏页无法逐出,这将增加内存压力, 两次刷新之间的间隔也会增加数据丢失的可能性。通过 v2 提案会尝试减少面向客户的部分性能成本 写入。

ZX_VMO_TRAP_DIRTY v2

将添加新的系统调用 zx_pager_set_dirty_pages_limit() 以指定 允许内核累积一定数量的脏页。通过 那么文件系统为这些工作负载预留了空间 脏页。这是一个每页的限制,默认设为 0。 可以使用 zx_pager_set_dirty_pages_limit()。从本质上讲,v1 设计 设置为零。

zx_status_t zx_pager_set_dirty_pages_limit(zx_handle_t pager_handle,
                                           uint64_t num_dirty_pages);

内核会跟踪每个分页器的脏页数(关于 符合稍后跟踪条件的网页),这就会增加 过渡到 Dirty,并在过渡到 Clean 时递减。内核 仍会像在 v1 中一样,捕获每个 Dirty 转换, 未完成的脏页数,前提是在不超出 分配的脏内存量。如果新计数未超过该限制,则内核 将在不涉及用户寻呼机的情况下继续写入。这是正常现象 作为正常操作模式,从而节省往返时间, 用户分页器。

如果使用此方法,用户寻呼机需要向 内核:

  1. 分页器范围内的脏数据量限制
  2. 脏污网页将被计入该限制

对于 2),我们再次依赖 ZX_VMO_TRAP_DIRTY VMO 创建标志。这个 标志现在会触发生成新类型的寻呼机请求: ZX_VMO_DIRTY_MODE。当内核 trap 现在写入内容时,它会查询 以确定是否应选择将这些网页纳入统计范围 使其低于脏上限用户寻呼机使用 zx_pager_op_range 进行响应,并返回 是两种新运算类型之一

  • ZX_PAGER_OP_DIRTY_MODE_POOLED 用于告知内核,该范围内的页面将 不计入每页的脏数据量限制。此字段适用于文件 在 CoW 模式下运行,在覆盖模式下运行稀疏文件区域。

  • ZX_PAGER_OP_DIRTY_MODE_UNPOOLED 用于告知内核,该范围内的页面 不会计入脏限制。它适用于非稀疏集群 以覆盖模式运行的稀疏文件所在的区域。

标记为 ZX_PAGER_OP_DIRTY_MODE_POOLED 的网页已转换为 Dirty,并且 待处理的分页器脏页数将递增,前提是它不超过 分页器脏页限制。如果对页面进行脏化,则会超出分页器脏污量限制 不过,内核开始生成 ZX_PAGER_VMO_DIRTY 数据包,即 默认模式(如 v1 中所述)。您可以提供一个可选标志, 在页面被写回(转换为 Clean)时进入脏模式, 可节省捕获未来写入操作以生成 ZX_VMO_DIRTY_MODE 的费用 寻呼机请求。

这种设计可实现灵活的模型,在该模型中,文件系统可以混合 各种写入模式类型以 CoW 模式写入的文件 他们使用 ZX_VMO_TRAP_DIRTY 创建的 VMO,并且他们的页面可以使用共用 模式。同样,您也可以使用 ZX_VMO_TRAP_DIRTY 标志,并将池化和取消池模式用于稀疏和 非稀疏区域。始终使用“覆盖”模式的文件可以省略 ZX_VMO_TRAP_DIRTY 标志,这样他们就永远无需向 写入时分页请求的费用。

用户寻呼机开始接收 ZX_PAGER_VMO_DIRTY 请求后, 脏配额,应开始清理页面,以便为 新的脏页。当使用 zx_pager_set_dirty_pages_limit()(使用与之前相同的限制或新的限制)。 在此调用后,内核将继续检查累积的脏计数 不超出未来写入时的脏数据量限制,并会生成 ZX_PAGER_VMO_DIRTY 则只有在再次达到脏页数限制时才会发出请求。

ZX_VMO_TRAP_DIRTY v1 与 v2 之间的区别

v1 和 v2 之间的主要区别在于,负责 来跟踪预订数量在 v1 中,文件系统负责 同时,内核会告知其 增加预留数量由于负责拦截的实体 预留(内核)的潜在更改与实体不同 执行实际的簿记操作(文件系统),因此需要紧密耦合 。在 v2 中,我们尝试让内核跟踪 预留计数本身因此需要与文件系统进行通信 仅当 - 1) 需要设置 VMO 范围以选择加入(或退出)内核时 预留跟踪;2) 内核耗尽预留配额,并且 需要文件系统进行干预。

我们预计 2) 属于极端情况,因为文件系统会定期 将脏页刷新到磁盘。预计大部分的沟通 原因:1)。内核可以多次请求同一范围的信息 (例如,对于跨越重叠范围的写入),同样,文件系统可以 多次向内核提供关于同一范围的冗余信息。 在页面上设置脏模式实际上并不会 因为脏污染计数仅在页面实际存在 写入。因此,文件系统还可以在页面上设置脏模式 推测性降低未来寻呼请求的性能成本 (不过,请注意,这可能会产生影响的页面逐出)。

发现脏范围

用户分页器需要一种机制来查找 VMO 中的脏页,因此它 因此您可以将它们写回去这种情况下,需要考虑两种不同的模式: 模型。 推送模型,其中内核通过发送用户分页器来指示脏页 回写请求。初始设计始于比较简单的拉取模型 并引入了查询系统调用的脏范围,可能如下所示:

// |pager| is the pager handle.
// |pager_vmo| is the vmo handle.
// |offset| and |length| specify the VMO range to query dirty pages within.
// Must be page-aligned.
//
// |buffer| points to an array of type |zx_vmo_dirty_range_t| defined as follows.
// typedef struct zx_vmo_dirty_range {
//   // Represents the range [offset, offset + length).
//   uint64_t offset;
//   uint64_t length;
//   // Any options applicable to the range.
//   // ZX_VMO_DIRTY_RANGE_IS_ZERO indicates that the range contains all zeros.
//   uint64_t options;
// } zx_vmo_dirty_range_t;
//
// |buffer_size| is the size of |buffer|.
//
// |actual| is an optional pointer to return the number of dirty ranges that were
// written to |buffer|.
//
// |avail| is an optional pointer to return the number of dirty ranges that are
// available to read. If |buffer| is insufficiently large, |avail| will be larger
// than |actual|.
//
// Upon success, |actual| will contain the number of dirty ranges that were copied
// out to |buffer|. The number of dirty ranges that are copied out to |buffer| is
// constrained by |buffer_size|, i.e. it is possible for there to exist more dirty
// ranges in [offset, offset + length) that could not be accommodated in |buffer|.
// The caller can assume than any range that had been made dirty prior to
// making the call will either be contained in |buffer|, or will have a start
// offset strictly greater than the last range in |buffer|. Therefore, the caller
// can advance |offset| and make another query to discover further dirty ranges,
// until |avail| is zero.
//
zx_status_t zx_pager_query_dirty_ranges(zx_handle_t pager,
                                        zx_handle_t pager_vmo,
                                        uint64_t offset,
                                        uint64_t length,
                                        void* buffer,
                                        size_t buffer_size,
                                        size_t* actual,
                                        size_t* avail);

用户寻呼机应该会多次调用此查询,从而推进 查询延迟,直到处理完所有脏页。

使用拉取模型时,网页清理速度完全取决于 文件系统选择查询脏范围并尝试 回写。但在少数情况下,内核本身需要 来发起回写页面的请求(例如在内存紧张时), 以便清理脏页,随后将其释放内核可能会向 在本例中,按 LRU 顺序针对脏页发出回写请求。这样做的目的是 作为对用户寻呼机的提示,以便其可以提高 刷新页面,例如是否有延迟处理请求

VMO 中对脏范围 [start, end) 的回写请求可能如下所示: 。

zx_packet_page_request_t request {
    .command = ZX_PAGER_VMO_WRITEBACK,
    .flags = ZX_PAGER_MEMORY_PRESSURE,
    // |offset| and |length| will be page-aligned.
    .offset = start,
    .length = end - start,
};

写回脏范围

zx_pager_op_range() 系统调用经过扩展,可支持两个额外的操作: ZX_PAGER_OP_WRITEBACK_BEGINZX_PAGER_OP_WRITEBACK_END,用于在以下情况下发出信号: 分别表示用户分页器开始刷新页面和刷新完成后。

  • ZX_PAGER_OP_WRITEBACK_BEGIN 会更改Dirty 指定范围为 AwaitingClean。对于 AwaitingCleanClean 状态,并保持这些状态不变。
  • ZX_PAGER_OP_WRITEBACK_END 可更改以下集群中任何 AwaitingClean 页面的状态: 将指定范围设置为 Clean。对于任何已经 Clean 或处于 Dirty 状态且状态保持不变的网页。

如果在执行刷新时遇到任何错误,即 ZX_PAGER_OP_WRITEBACK_BEGIN,但在 ZX_PAGER_OP_WRITEBACK_END 之前, 只需分页器即可执行更多操作。这些网页将保留在AwaitingClean中 状态(假设没有进行另一次写入)。当 内核再次查询脏页,将包括 AwaitingClean 页 以及 Dirty 网页,然后用户寻呼机可以尝试回写这些网页 失败的网页。

// Supported |op| values are:
// ZX_PAGER_OP_WRITEBACK_BEGIN indicates that the user pager is about to
// begin writing back the specified range and the pages are marked |AwaitingClean|.
// ZX_PAGER_OP_WRITEBACK_END indicates that that user pager is done writing
// back the specified range and the pages are marked |Clean|.
//
// |pager| is the pager handle.
// |pager_vmo| is the VMO handle.
// |offset| and |length| specify the range to apply the |op| to, i.e. [|offset|,
// |offset| + |length|).
// For ZX_PAGER_OP_WRITEBACK_*, |data| is unused and should be 0.
zx_status_t zx_pager_op_range(zx_handle_t pager,
                              uint32_t op,
                              zx_handle_t pager_vmo,
                              uint64_t offset,
                              uint64_t length,
                              uint64_t data);

对于 ZX_PAGER_OP_WRITEBACK_BEGIN,可以选择将 data 设置为 ZX_VMO_DIRTY_RANGE_IS_ZERO,以指示调用方想要回写 指定范围为零。此属性应在调用方 正在处理由 zx_pager_query_dirty_ranges() 返回的范围及其 options 已设为 ZX_VMO_DIRTY_RANGE_IS_ZERO。它可确保任何非零值 在查询之后但在回写之前范围内创建的内容 并没有丢失,因为错误地假定它仍然为零,并将其标记为 清除(因此可逐出)。

调整 VMO 的大小

由分页器支持的 VMO 与匿名(非分页器支持的)VMO 的不同之处在于: VMO 中没有内容的处理。匿名 VMO 具有隐式初始值 内容为零,因此未承诺页面表示为零。对于这种情况,并非如此 由分页器支持的 VMO,其中未提交的网页不表示零;它们仅仅是指 。将大小调整为 更大的大小,但是分页器无法提供新扩展中的页面 范围,只是因为在后备源(例如 所以就不需要分页了内核可以为 零。

通过跟踪跨新大小调整的零间隔来处理大小调整 范围,内核隐式地为其提供零个页面。用户分页器 还不知道这个零区间,因此当 用户分页器查询脏范围。此外, 对于此范围,zx_vmo_dirty_range_t已设为 ZX_VMO_DIRTY_RANGE_IS_ZERO 表明该位置全部为零。

如果使用 ZX_VMO_TRAP_DIRTY 标志创建了 VMO,并且页面 写入到这个新扩展的范围内,内核会生成 在提交前先收到 ZX_PAGER_VMO_DIRTY 个寻呼请求。这是 因为文件系统可能需要为实际(非零)页面预留空间。 该模型假设 0 可以在磁盘上通过 稀疏区域,因此仅当 都在新的扩展范围内投入使用

将 VMO 与分页器分离

zx_pager_detach_vmo() 会将 ZX_PAGER_COMPLETE 数据包加入队列,这指示 用户寻呼机应该不会再收到针对该 VMO 的寻呼请求 。这也表示用户分页器应查询并写回任何 未完成的脏页。请注意,分离操作不会阻塞, 网页已被写回;它只是通知用户分页器 可能性。

分离后,zx_vmo_read() / zx_vmo_write()(本来需要 要生成的寻呼机请求失败并显示 ZX_ERR_BAD_STATE。读取和写入 通过映射,这些映射同样会导致所需的寻呼请求生成 严重页面错误异常。内核可以随意从 VMO。不过,内核会保留脏页,直到脏页被 用户寻呼机。也就是说,ZX_PAGER_OP_WRITEBACK_BEGIN 和 即使在该日期之后,VMO 仍继续支持ZX_PAGER_OP_WRITEBACK_END 已分离。在所有其他操作上计算zx_pager_op_range() 已分离的 vmo 上的 zx_pager_supply_pages() 失败并显示 ZX_ERR_BAD_STATE

如果分页器因相关 VMO 中的脏页而被销毁,则内核 因此可以自由移除这些网页,而不管是否存在 未完成的回写请求。也就是说,脏页肯定是 它们才会保存在内存中。

查询分页器 VMO 统计信息

内核还会跟踪 VMO 是否已被修改,可由 用户寻呼机。用户寻呼机需要使用此字段来跟踪 mtime

// |pager| is the pager handle.
// |pager_vmo| is the VMO handle.
// |options| can be ZX_PAGER_RESET_VMO_STATS to reset the queried stats.
// |buffer| points to a struct of type |zx_pager_vmo_stats_t|.
// |buffer_size| is the size of the buffer and should be large enough to
// accommodate |zx_pager_vmo_stats_t|.
//
// typedef struct zx_pager_vmo_stats {
//   uint32_t modified;
// } zx_pager_vmo_stats_t;
zx_status_t zx_pager_query_vmo_stats(zx_handle_t pager,
                                     zx_handle_t pager_vmo,
                                     uint32_t options,
                                     void* buffer,
                                     size_t buffer_size);

返回的 zx_pager_vmo_stats_tmodified 字段设置为 如果 VMO 已修改,则为 ZX_PAGER_VMO_STATS_MODIFIED,否则为 0。通过 将来可以使用更多字段扩展 zx_pager_vmo_stats_t 结构体 用户寻呼机可能认为有用的查询字词。

在修改 VMO 的系统调用中,modified 状态会更新,例如 zx_vmo_write()zx_vmo_set_size(),以及首次写入页面时 是通过映射来实现故障的页面上的第一个写入错误是 正确管理从 CleanDirty 的转换,因此 然后,modified 状态会更新。通过 但不会跟踪脏页这样做会显著降低 写入映射的 VMO。因此,modified 状态可能并不完全准确 映射的 VMO。

options 可以为 ZX_PAGER_RESET_VMO_STATS 如果用户寻呼机也希望 重置查询的统计信息。options 值为 0 不会重置任何状态, 会执行纯查询。请注意,此调用可能会影响未来的 如果存在以下情况,则使用可查询状态调用 zx_pager_query_vmo_stats(): 已指定 ZX_PAGER_RESET_VMO_STATS 选项。例如,如果 “zx_vmo_write()”后跟两个连续的 zx_pager_query_vmo_stats() 使用 ZX_PAGER_RESET_VMO_STATS 选项的调用,只有第一个调用 查看 modified 集。由于在首次更新后没有进行任何其他修改, zx_pager_query_vmo_stats(),第二个 zx_pager_query_vmo_stats() 将 返回 modified 为 0。

实现

分页器回写已经开发了一段时间,其中包含了所有新的 API 部分, 适用于 @next vDSO。fxfs 已采用回写 API 来支持 数据流读/写和 mmap

性能

借助分页器回写,fxfs 能够从执行 I/O 切换到 这使得效果提升约 40-60 倍 改进了各种基准测试

安全注意事项

无。

隐私注意事项

无。

测试

已编写内核核心测试和压力测试来练习分页器 系统调用。此外还有存储测试和性能基准测试。

文档

更新了内核系统调用文档。

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

速率限制回写请求

在未来的迭代中,当内核生成回写请求时(在 内存压力或稳定的后台速率),我们需要某种 用于控制在寻呼机端口排队的回写请求数的策略。 一种选择是让内核跟踪 并尽量将其控制在一定的限制范围内

另一种方法是让用户分页器配置可调优参数,以确定 直接或间接地回写请求生成速率。例如: 用户寻呼机可以指定建议的网页保留时长 分页器可以支持写入。可能有文件系统需要后台运行 回写速率远高于全局系统默认值。也可能是 有助于用户分页器指定粒度(系统页面的倍数) 以最大尺寸处理请求。然后,内核会将此 计算范围时,可能可以生成 。

跟踪和查询网页存在时间信息

对于初始实现,内核会跟踪页面队列中的脏页, 排序依据是页面首次更新的时间此队列可用于 这样,如果以后生成回写请求 它们可以从脏队列移至(干净)分页器支持的队列中, 目前可跟踪只读页面并存在时间限制。我们可能需要更精细地 还可以跟踪脏页的存在时间;统一脏话和整洁 将页面移到一个通用池中,以利用老化和已访问位跟踪 w.r.t.一个全局工作集。我们还可能会泄露这些年龄信息 通过新的 API 传递给用户分页器,以便在处理时将其考虑在内 回写请求。

在回写期间阻止进一步的写入

此处提出的设计不会阻止 正在回写,即在 ZX_PAGER_OP_WRITEBACK_BEGINZX_PAGER_OP_WRITEBACK_END。相反,被写入的网页简单地 重新标记为脏状态某些文件系统可能希望 AwaitingClean 状态的网页。我们可以考虑添加一个 ZX_PAGER_OP_WRITEBACK_BEING_SYNC(将来会屏蔽),这会在 回写。请注意,ZX_VMO_TRAP_DIRTY v1 确实提供了一种解决此问题的方法 若是 ZX_PAGER_VMO_DIRTY 分页请求,文件系统可以暂缓发送 。

先验技术和参考资料