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

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

定义 Zircon 中的异步操作如何与对象和句柄操作交互。

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

摘要

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

设计初衷

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

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

利益相关方

Zircon

教员

由 FEC 任命的负责引导此 RFC 完成 RFC 流程的人员。

Reviewers:

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

咨询了

社交

要求

定义异步内核操作(包括 object_wait_async)之间的关系,以及处理传输和关闭句柄等互动。

设计

概览

Zircon 中的异步操作对对象(而非句柄)执行操作。对象状态的变化可能会影响这些操作。用于引用对象的句柄的更改不会影响异步操作的行为,但下文中会对通道进行专门说明。注册后,异步操作会持续执行,直到完成、被明确取消或对象的所有句柄都关闭为止。用于取消端口上异步操作的设施已更新,以反映新的语义。

系统调用变更

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_manyhandle_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 注册异步等待,并通过将已注册的等待标记为已取消并在观察器触发时清理观察器状态来执行异步等待取消。

性能

这项更改减少了对异步操作的内核预订量,并降低了对句柄表锁的压力,这在某些类型的负载下可能有所帮助。我们目前的微基准测试表明,使用简单的原型实现没有明显变化。为了取消已注册的异步操作,用户空间库还可以存储更少的信息。

工效学设计

因此,如果应用希望取消单个异步操作,则需要跟踪键值。在实践中,调度程序实现已执行此操作(除了保留句柄值之外)。

向后兼容性

这会以细微的方式更改 object_wait_asyncport_cancel 的已记录和实际语义。这些变更不会影响维护以下媒体资源的计划:

  • 异步等待会使用每个等待的唯一键进行注册
  • 等待的对象的句柄未关闭或转移,并且注册的等待仍处于待处理状态

这些观点在当今社会通常是正确的。异步等待通常与状态分配相关联,以处理等待结果,并且这些等待的 key 通常基于此分配的地址(该地址在地址空间内必须是唯一的)或其他形式的唯一标识符进行计算。句柄通常存储在拥有者数据结构中,关闭句柄需要完成某种关闭过程,以清除对它的引用。

https://fxrev.dev/984701 是一个原型,会忽略 source 参数对 port_cancel 的影响。有一个 Zircon 核心测试会故意重复使用密钥,树中的所有其他测试用例均会在未经修改的情况下通过。这表明对 port_cancel 所做的更改与当前代码兼容。

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

安全注意事项

此提案允许进程在某些情况下观察不再拥有句柄的对象状态的变化。在此模型中,句柄表示启动异步操作的功能。除了具有不可重复句柄的对象(最显著的是通道)的特殊情况外,我们通常不保证句柄代表对对象的独占访问权限。

对于授予可复制句柄的大多数对象类型,拥有句柄的进程无法推理出该句柄上存在哪些其他句柄和操作,除非它知道该句柄从创建到从可信来源传输的完整历史记录。如果我们允许在传输对对象的句柄后让异步操作在对象上保留,此分析不会发生明显变化,因为注册器可以轻松保留重复的句柄。

具体而言,由于我们始终会授予不可重复的标识名,因此我们保证对拥有频道标识名的进程具有独占访问权限。允许频道标识名的前所有者观察频道在转移后首次变为可读的时间可能不属于敏感安全信息。在引入与通过渠道发送的消息内容互动的操作时,我们需要谨慎行事,以便仅允许当前的句柄所有者执行这些操作。

测试

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

文档

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

缺点、替代方案和未知情况

基于手柄的其他语义

一种替代方案是继续将异步操作与其发起句柄相关联(如当前的一些文档所建议),并在添加新的异步操作时维持这种关系。这会增加这些新操作的复杂性,并使重构和优化内核的句柄管理逻辑变得更加困难,同时对应用逻辑提供的效用很小。

port_cancel 重复使用

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

在先技术和参考文档

Zircon 句柄解析模型 - 2020 年的 RFC 草稿,探讨了其中的一些问题。

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

异步循环密钥管理

zxwait 密钥管理

fuchsia-async 密钥管理