RFC-0240:异步操作在对象上

RFC-0240:异步操作在对象上
状态已接受
领域
  • 内核
说明

定义 Zircon 中的异步操作如何与对象交互并处理操作。

Gerrit 更改
  • 985309
作者
审核人
提交日期(年-月-日)2024-02-06
审核日期(年-月-日)2024-03-11

摘要

这提议 Zircon 内核中的异步操作是对内核对象执行的,并且未与用于注册这些操作的句柄相关联。

设计初衷

Zircon 定义了对内核对象的异步等待操作。我们希望添加会带来副作用的异步操作,例如异步通道读取。为此,我们需要确定这些操作的语义,以及它们与涉及的句柄和/或对象上的其他操作之间的交互。

对象_wait_async 就是一个异步操作示例,在对象的状态发生变化时生成端口数据包。当前的 API 会尝试拆分对底层对象执行的操作(正如名称所暗示的)与标识操作的句柄之间的差异。

利益相关方

锆石

教员

由 FEC 指定的人员,负责通过 RFC 流程照管此 RFC。

审核者

  • maniscalco@google.com
  • cpu@google.com
  • abarth@google.com

咨询人员

社交

要求

定义异步内核操作(包括 object_wait_async 和句柄交互(如传输和关闭句柄)的关系)。

设计

概览

Zircon 中的异步操作是对对象(而不是句柄)执行操作。对象状态的变化可能会影响这些操作。用于引用对象的句柄的更改不会影响异步操作的行为,下面将对通道进行 carveout,具体如下所述。注册后,异步操作会一直持续,直到操作完成、被明确取消或对象的所有句柄都关闭。取消端口异步操作的功能已更新,以反映新的语义。

系统调用变化

object_wait_async:

  • 从文档和后备代码中移除“如果句柄关闭,与其关联的操作也会终止,但队列中已有的数据包不会受到影响”。

注册后,异步等待将一直有效,直到对象状态更改为与观察到的条件匹配、等待被明确取消,或对象的所有句柄都关闭。

port_cancel:

  • 废弃了 port_cancel 并提供 port_cancel_key 作为替代,该方法能够取消使用指定密钥在指定端口上的所有操作。签名:
zx_port_cancel_key(zx_handle_t port, uint32_t options, uint64_t key)

您还可以添加 port_cancel 目前缺少的 options 参数。

另一种方法是更新 port_cancel 的实现以忽略 source 参数。在实际操作中,可取消的端口操作是使用唯一密钥注册的。通常,此键是分配用于处理操作本身的数据结构的地址。有证据表明这项更改是安全的(请参阅“向后兼容性”部分),但安全地部署这项更改并在使用相同名称的情况下收回 options 槽位并非易事。为操作使用新名称让我们能够逐步更新代码,并跟踪旧操作的调用方。

object_wait_oneobject_wait_many 以及 handle_close

  • 明确记录的功能集中没有变化。有关在同步等待期间关闭句柄的文档已被弃用。

这些是对由句柄标识的一个或多个对象执行的同步等待。我们目前记录了,关闭句柄会通过这些操作取消任何当前待处理的等待,但这本质上是少儿不宜的,因为关闭句柄的线程无法知道调用 object_wait_{one,many} 的另一个线程是否已解析该句柄。对 handle_closeobject_wait_{one,many} 竞争的程序可以观察 ZX_ERR_CANCELEDZX_ERR_BAD_HANDLE。我们可以提供一种无竞态的方式来取消这些同步等待操作,方法是将句柄与对象分离 (https://fxrev.dev/949517),或者让程序更轻松地等待多个条件,以便它们可以同步对用户空间中的句柄的访问权限。由于当前在操作中,句柄需要保持有效状态的时间不明确,因此此方案不会更改行为。

channel_callchannel_call_etc

  • 明确记录的功能集中没有变化。

这些当前隐式文档记录了与 ZX_ERR_CANCELED 返回值中的 object_wait_{one,many} 相同的取消行为。通常情况下,使用 handle_cancel 时,它们也会出现与这些操作相同的竞态问题,但由于此操作会公开更多内部细节,因此可以构建在实践中接近不会竞态的场景。在超时后,调用方无法对 channel_call 的内部等待进行参数化,因此程序无法使用多个条件来协调关闭操作。为了提供无竞态取消功能,我们需要定义一项单独的操作,例如 https://fxrev.dev/949517 中提议的操作。与 object_wait_{one,many} 一样,尽管当前行为可疑,但 RFC 未建议对这些操作的行为进行任何更改。

频道排序

通道在 Fuchsia 系统架构中发挥着独特的作用,目前在内核中有特殊情况逻辑,用于验证仅当句柄归调用进程所有时,对通道句柄的操作才会成功。渠道在系统中被广泛使用以交换数据和功能。这些检查的目的是在以不同信任级别运行的进程之间传递句柄时,保持通道的机密性和完整性。一旦某个进程获得某个通道的句柄,该进程就会获得从该端点读取和写入消息的独占访问权限。为了保留此属性具有此 RFC 中提议的语义并允许将来的异步通道操作,我们可以利用通道的另一个属性,即通道端点只有一个句柄。针对通道句柄的转移操作可被视为对对象本身的操作,我们可能会导致针对通道的待处理更改操作或转移尝试失败。

观察对象状态(例如 READABLE 和 WRITABLE 信号)不会违反我们关注的属性。可以在转出此句柄后,程序可以观察到某个通道何时可读取或可写入,因为该信息不包含有关系统状态的功能或重要机密信息。这意味着,对于采用此方案的频道,object_wait_async 操作不需要采取不同的行为方式。将来对通道的操作(例如异步读取)需要考虑与传输的交互。

实现

这项更改对用户空间的实际影响主要是在于取消端口对象上的异步对象等待操作。异步调度程序库将需要为可取消的操作分配和跟踪唯一键(它们已经这样做了),并且如果它们未使用对象,则不再需要跟踪句柄值。通过将第二个参数从句柄值更改为字面量 0,对 port_cancel 的调用将机械转换为 port_cancel_key

在内核端,观察者、端口和对象表示法之间的关系非常微妙。这项变更会在取消过程中移除对句柄表锁的依赖,并支持将来对句柄管理逻辑进行重构,从而降低一定的复杂性。在短期内,实现此更改的最直接方法是使用 null Handle 注册异步等待,然后通过将已注册的等待标记为已取消并在观察器触发时清除观察器状态来执行异步等待取消。

性能

这项变更可减少对异步操作进行内核 bookke 的次数以及对句柄表锁的压力,这在某些类型的负载下可能有所帮助。我们目前的 Microbenchmark 显示,在简单的原型实现方面没有显著变化。用户空间库也可以存储更少的信息,以便取消已注册的异步操作。

工效学设计

这样,如果应用想要取消各个异步操作,就需要跟踪键值对。实际上,调度程序实现已经这样做了(除了保留句柄值之外)。

向后兼容性

这会以细微的方式改变 object_wait_asyncport_cancel 的记录语义和实际语义。这些变更不会影响维护这些属性的程序:

  • 异步等待在每次等待中使用唯一键进行注册
  • 处于等待状态的对象的句柄未关闭,或者当已注册的等待状态仍在等待处理时传输

目前通常都是如此。异步等待通常与状态分配相关联,以处理等待结果,这些等待结果的 key 通常根据此分配的地址(在地址空间中必须是唯一的)或另一种形式的唯一标识符。句柄通常存储在其所属的数据结构中,关闭句柄需要执行某种关闭过程来清除对它的引用。

https://fxrev.dev/984701 是一个原型,它会忽略 port_cancelsource 参数。有一项 Zircon 核心测试有意重复使用密钥,而树中的所有其他测试用例均不进行修改。这表明对 port_cancel 的更改与当前代码兼容。

https://fxrev.dev/986494 是一个相关原型,用于将已注册句柄的生命周期与观察器分离开来。此评估可评估我们是否依赖关闭句柄来取消未完成的异步等待。我们的所有现有测试均未经过修改地通过这一变更,这表明行为方面的这一变更与当前代码兼容。

安全注意事项

此方案允许进程观察在某些情况下它们不再无法处理的对象状态变化。在此模型中,句柄表示启动异步操作的功能。一般来说,我们并不保证句柄代表对某个对象进行独占访问,这属于具有不可复制句柄的对象(最值得注意的是通道)的特殊情况之外。

对于授予可复制进程的大多数对象类型,如果进程拥有某个句柄,就无法推断该句柄上存在哪些其他句柄和操作,除非它了解该句柄的完整历史记录,然后再返回创建或从可信来源进行转移。如果我们允许异步操作在对象的句柄被转移后持久保存,则此分析不会发生明显变化,因为注册商可以轻松地保留重复的句柄。

特别是对于频道,我们一直会授予不可复制的句柄,我们承诺会对具有频道句柄的进程进行独占访问。让频道句柄的前任所有者观察某个通道在转移后首次变为可读状态的时间,这可能不安全敏感。在引入与通过通道发送的消息内容交互的操作时,我们需要格外小心,只有当前的句柄所有者才能执行这些操作。

测试

对端口和异步对象行为的更改将使用 Zircon 的核心测试套件进行测试。我们将通过检查库及其测试套件来评估库兼容性(尤其是调度程序库中的兼容性)。我们的现有集成套件通过新行为的原型设计(请参阅“向后兼容性”部分),我们可以在将更改部署到 object_wait_async 时密切监控这些情况。

文档

需要为新的 port_cancel_key 添加系统调用文档,并且需要按照“设计”部分所述更新现有操作的文档。

缺点、替代方案和问题

基于句柄的替代语义

一种替代方案是继续将异步操作与其启动句柄相关联(正如当前文档的一些建议所述),并在我们添加新的异步操作时保持这种关系。这会增加这些新操作的复杂性,使其更难重构和优化内核的句柄管理逻辑,同时对应用逻辑几乎毫无用处。

port_cancel再利用

我们可以用大小相同的 options 参数替换现有 port_cancel 上的 source 参数,而无需定义新的 port_cancel_key 系统调用。对于过渡期,不支持任何选项,并且也允许在此字段中使用处理值。在此过渡期间,我们会查找并更新 port_cancel 的所有调用方,以在此字段中传递 0 值,而不是传递它们当前提供的句柄值。在过渡期结束时,我们将开始强制将该字段设为 0,此时我们可以开始引入选项值。这种方法的挑战在于,可能很难检测到所有调用方都已更新为新语义。在实践中,在异步等待触发之前将其取消的情况非常少,因此对 port_cancel 的动态分析可能无法找到仅在异常错误处理或计时情况中才会到达的调用方。

现有艺术和参考资料

Zircon 句柄解决模型 - 2020 年发布的 RFC 草稿,旨在探索其中一些问题。

zx_handle_cancel - 草稿 RFC,定义一种机制来取消对句柄的同步操作,从而避免句柄解析争用。

异步循环密钥管理

zxwait 密钥管理

fuchsia-async 密钥管理