| RFC-0240:异步操作针对对象 | |
|---|---|
| 状态 | 已接受 |
| 区域 |
|
| 说明 | 定义了 Zircon 中的异步操作如何与对象和句柄操作进行交互。 |
| Gerrit 更改 | |
| 作者 | |
| 审核人 | |
| 提交日期(年-月-日) | 2024-02-06 |
| 审核日期(年-月-日) | 2024-03-11 |
摘要
此提案建议,Zircon 内核中的异步操作在内核对象上执行,并且不与用于注册这些操作的句柄相关联。
设计初衷
Zircon 定义了针对内核对象的异步等待操作。我们希望添加具有副作用的异步操作,例如异步渠道读取。为此,我们需要确定这些操作的语义以及它们与所涉及句柄和/或对象的其他操作的交互。
object_wait_async 是一个异步操作示例,当对象的状态发生变化时,该操作会生成一个端口数据包。当前 API 尝试拆分对底层对象执行的操作(顾名思义)与标识操作的句柄之间的差异。
利益相关方
Zircon
辅导员:
由 FEC 指派负责引导此 RFC 完成 RFC 流程的人员。
审核者:
- maniscalco@google.com
- cpu@google.com
- abarth@google.com
已咨询:
共同化:
要求
定义异步内核操作(包括 object_wait_async)的关系,并处理句柄转移和关闭等互动。
设计
概览
Zircon 中的异步操作针对对象(而非句柄)运行。对象状态的变化可能会影响这些操作。用于引用对象的句柄的更改不会影响异步操作的行为,但下文专门描述的渠道除外。注册后,异步操作会一直持续,直到完成、被明确取消或对象的所有句柄都已关闭。更新了用于取消端口上异步操作的功能,以反映新的语义。
系统调用变更
object_wait_async:
- 从文档和支持代码中移除了以下句子:“如果句柄已关闭,与其关联的操作也会终止,但队列中已有的数据包不受影响。”
注册后,异步等待会一直有效,直到对象状态发生变化以匹配观察到的条件、等待被明确取消或对象的所有句柄都已关闭。
port_cancel:
- 弃用
port_cancel并提供port_cancel_key作为替代项,该替代项使用指定密钥取消指定端口上的所有操作。Signature:
zx_port_cancel_key(zx_handle_t port, uint32_t options, uint64_t key)
这也是添加 port_cancel 目前缺少的 options 参数的好机会。
另一种方法是更新 port_cancel 的实现,以忽略 source 参数。实际上,可取消的端口操作是按每个操作使用唯一键注册的。此键通常是为处理操作本身而分配的数据结构的地址。有证据表明,此更改是安全的(请参阅“向后兼容性”部分),但如果使用相同的名称,则很难安全地部署此更改并回收 options slot。为操作使用新名称将有助于我们以增量方式更新代码,并跟踪旧操作的调用方。
object_wait_one 和 object_wait_many(含 handle_close):
- 明确记录的功能集没有变化。在同步等待期间关闭句柄的文档已被弃用。
这些是对由句柄标识的一个或多个对象的同步等待。我们目前记录的是,关闭句柄会通过这些操作取消任何当前待处理的等待,但这本身就存在竞态条件,因为关闭句柄的线程无法知道调用 object_wait_{one,many} 的另一个线程是否已解析该句柄。一个程序同时运行 handle_close 和 object_wait_{one,many},可能会观察到 ZX_ERR_CANCELED 或 ZX_ERR_BAD_HANDLE。我们可以通过将句柄与对象分离 (https://fxrev.dev/949517) 或通过使程序更轻松地等待多个条件,从而提供一种无竞争的方式来取消这些同步等待操作,以便它们可以在用户空间中同步对句柄的访问。由于目前尚不清楚在操作中何时需要有效的句柄,因此本提案不会更改此行为。
channel_call 和 channel_call_etc:
- 明确记录的功能集没有变化。
这些函数目前具有隐式文档,其中说明了与 object_wait_{one,many} 在 ZX_ERR_CANCELED 返回值中相同的取消行为。在一般情况下,当使用 handle_cancel 时,它们也会遇到与这些操作相同的竞态问题,不过由于此操作会公开更多内部细节,因此可以设计出在实践中不会发生竞态的场景。channel_call 的内部等待时间无法由调用方通过超时时间进行参数化,因此程序无法使用多个条件来协调关闭。为了提供无竞态的取消,我们需要定义一个单独的操作,例如 https://fxrev.dev/949517 中提出的操作。与 object_wait_{one,many} 一样,虽然当前行为可疑,但此 RFC 并未提议对这些操作的行为进行任何更改。
频道排序
渠道在 Fuchsia 系统架构中发挥着独特的作用,目前在内核中具有特殊情况逻辑,用于验证只有当渠道句柄归调用进程所有时,对该渠道句柄的操作才会成功。渠道广泛用于在系统中交换数据和功能。这些检查的目的是在不同信任级别的进程之间传递句柄时,维护渠道的机密性和完整性。一旦进程获得对某个渠道的句柄,它就可以独占方式从该端点读取和写入消息。为了保留此属性并使用此 RFC 中提出的语义,同时允许未来的异步渠道操作,我们可以利用渠道的另一个属性,即渠道端点只有一个句柄。对渠道句柄的转移操作可以视为对对象本身的操作,我们可以导致渠道上待处理的突变操作或转移尝试失败。
对对象状态(例如 READABLE 和 WRITABLE 信号)的观测不会违反我们关心的属性。即使在转移此句柄后,程序仍能观察到通道何时可读或可写,这是可以接受的,因为此信息不包含有关系统状态的功能或重要机密信息。这意味着,对于采用此提案的渠道,object_wait_async 操作无需有不同的行为。未来对渠道(例如异步读取)的操作将需要考虑与转移的互动。
实现
此更改对用户空间的主要实际影响是取消对端口对象上的异步对象等待。异步调度程序库需要为可取消的操作分配和跟踪唯一键(它们已经这样做),并且如果它们不使用对象,则不再需要跟踪句柄值。对 port_cancel 的调用将通过将第二个参数从句柄值更改为字面值 0 机械地转换为 port_cancel_key。
在内核端,观察者、端口和对象表示之间的关系非常微妙。此更改将通过在取消过程中移除对句柄表锁的依赖来缓解一些复杂性,并为将来重构句柄管理逻辑做好准备。从短期来看,实现此变更的最直接方法是注册具有 null Handle 的异步等待,并通过以下方式执行异步等待取消:在观察者触发时,将注册的等待标记为已取消并清理观察者状态。
性能
此更改减少了内核对异步操作的簿记量,并减轻了句柄表锁的压力,这可能有助于应对某些类型的负载。我们当前的微基准测试显示,简单的原型实现没有显著变化。用户空间库还可以存储较少的信息,以便取消已注册的异步操作。
工效学设计
如果应用希望取消各个异步操作,则需要跟踪键值。实际上,调度器实现已执行此操作(除了保留句柄值之外)。
向后兼容性
这会以细微的方式更改 object_wait_async 和 port_cancel 的文档记录语义和实际语义。这些变更不会影响具有以下属性的计划:
- 异步等待会注册每个等待的唯一键
- 等待的对象句柄未关闭或转移,且注册的等待仍处于待处理状态
这些说法在今天通常仍然适用。异步等待通常与状态分配相关联,以处理等待结果,并且这些等待的 key 通常基于此分配的地址(在地址空间内必须是唯一的)或其他形式的唯一标识符进行计算。句柄通常存储在所有数据结构中,关闭句柄需要执行某种关闭程序来清除对它的引用。
https://fxrev.dev/984701 是一个原型,它会忽略 port_cancel 的 source 参数。有一个 Zircon 核心测试会故意重复使用密钥,树中的所有其他测试用例均通过未修改。这表明对 port_cancel 的更改与当前代码兼容。
https://fxrev.dev/986494 是一个相关原型,用于将已注册句柄的生命周期与观察者分离。此变量用于评估我们是否依赖于关闭句柄来取消未完成的异步等待。我们所有现有的测试都通过了未修改的测试,这表明此行为更改与当前代码兼容。
安全注意事项
此提案允许进程在某些情况下观察到它们不再具有句柄的对象状态的变化。在此模型中,句柄表示启动异步操作的能力。一般来说,我们不保证句柄表示对对象的独占访问权限,但具有不可复制句柄的对象(最值得注意的是渠道)除外。
对于授予可复制句柄的大多数对象类型,拥有句柄的进程无法推断出该句柄的其他句柄和操作,除非它了解该句柄的完整历史记录(从创建或从可信来源转移开始)。如果我们允许在句柄转移后,异步操作继续在对象上执行,那么这种分析不会发生显著变化,因为注册器同样可以轻松保留重复的句柄。
对于频道,由于我们始终授予不可重复的标识名,因此我们承诺拥有频道标识名的进程可以独占式访问该频道。允许频道标识名的前所有者在转让频道标识名后观察频道首次变为可读状态的时间,这可能不会涉及安全敏感信息。在引入与通过渠道发送的消息内容互动的操作时,我们需要谨慎处理,确保只有当前标识名所有者才能执行这些操作。
测试
端口和异步对象行为的更改将通过 Zircon 的核心测试套件进行测试。我们将通过检查库及其测试套件来评估库兼容性(尤其是在调度程序库中)。我们现有的集成套件通过了新行为的原型测试(请参阅“向后兼容性”部分),并且在将更改部署到 object_wait_async 时,我们可以密切监控这些套件。
文档
需要为新的 port_cancel_key 添加系统调用文档,并按照设计部分中的说明更新现有操作的文档。
缺点、替代方案和未知因素
基于句柄的替代语义
一种替代方案是继续将异步操作与其启动句柄相关联(如当前的一些文档所建议的那样),并在添加新的异步操作时保持这种关系。这会增加这些新操作的复杂性,并使重构和优化内核的句柄管理逻辑变得更加困难,同时对应用逻辑的实用性很小。
port_cancel重复使用
我们可以将现有 port_cancel 上的 source 参数替换为大小相同的 options 参数,而不是定义新的 port_cancel_key 系统调用。在过渡期内,此字段将不支持任何选项,但允许使用句柄值。在此过渡期间,我们会找到并更新 port_cancel 的所有调用方,以在此字段中传递 0 值,而不是它们当前提供的句柄值。在过渡期结束时,我们会开始强制要求该字段为零,届时我们就可以开始引入选项值。此方法存在一个问题,即可能难以检测到何时所有调用方都已更新为新语义。在实践中,很少会在异步等待触发之前取消它,因此对 port_cancel 的动态分析可能找不到仅在异常错误处理或时间安排情况下才到达的调用者。
在先技术和参考资料
Zircon 句柄解析模型 - 2020 年的 RFC 草稿,探讨了其中一些问题。
zx_handle_cancel - 定义了一种机制的草稿 RFC,用于取消句柄上的同步操作,避免句柄解析竞争。