RFC-0009:Edge 触发的 async_wait

RFC-0009:边缘触发了 async_wait
状态已接受
领域
  • 内核
说明

方案是,如果指定了 ZX_WAIT_ASYNC_EDGE 标记,则不考虑初始检查,并将信号集添加到 DispatchObject 的兴趣列表(无论初始信号状态如何)。在这种操作模式下,必须有一个信号从非活动状态转换为活动状态,数据包才能在提供的端口上排队(可能需要信号在进程中变为非活动状态)。

问题
  • 45709
  • 62553
Gerrit 更改
  • 442834
作者
审核人
提交日期(年-月-日)2020-10-24
审核日期(年-月-日)2020-11-06

总结

等待对对象断言信号通常由电平触发,并且会在 zx_object_wait_async 开始时执行检查(如果信号已处于活动状态),此时数据包会立即发送到端口。此 RFC 涉及向 zx_object_wait_async ZX_WAIT_ASYNC_EDGE 添加一个选项,该选项不执行该初始检查,因此只有在调用后信号从非活跃状态转换为活跃状态时,才会生成数据包。

可能因为对象上的信号已处于活动状态时,通过 ZX_WAIT_ASYNC_EDGE 调用了 zx_object_wait_async。在这种情况下,只有当对象上的信号变为非活动状态后,数据包才会在端口上排队,随后断言。事实上,这正是 ZX_WAIT_ASYNC_EDGE 的常用方式。

设计初衷

Linux 中的 epoll 轮询机制可以采用两种模式运行:电平触发和边缘触发。Fuchsia 的等待功能(尤其是 zx_object_wait_asynczx_port_wait)已经使关卡触发的轮询成为可能。但是,边缘触发的轮询需要能够等待一个处于活跃状态的对象上的信号(通过 I/O 预计会变为非活跃状态,随后再次处于活跃状态),从而在此后续信号转换时在端口上将数据包加入队列。 这是 ZX_WAIT_ASYNC_EDGE 的意图。

设计

幸运的是,zx_object_wait_asyncZX_WAIT_ASYNC_EDGE 标志的实现非常简单。

目前,如果其中一个信号集已处于活动状态,则直接调用观察者的 OnMatch 方法,无需执行任何进一步操作。否则,如果没有活跃的信号集,系统会通过所提供的 SignalObserver 将该信号集添加到 DispatchObject 的兴趣列表。

我们的方案是,如果指定了 ZX_WAIT_ASYNC_EDGE 标志,则不考虑初始检查,并将信号集添加到 DispatchObject 的兴趣列表(无论初始信号状态如何)。在这种操作模式下,其中一个信号必须从非活动状态转换为活动状态,数据包才能在提供的端口上排队(可能需要信号在进程中变为非活动状态)。

在 epoll 中使用 ZX_WAIT_ASYNC_EDGE 并触发 EPOLLET 边缘触发

此变更的主要用途是使用 epoll 中的 EPOLLET 标志启用边缘触发的轮询。Zircon 中的等待与 epoll 中的轮询不同,因为使用 epoll_ctl/EPOLL_CTL_ADD 将文件描述符添加到 epoll 文件描述符中,并持续监控,直到使用 epoll_ctl/EPOLL_CTL_DEL 将其移除。Zircon 等待(尤其是使用 zx_object_wait_async 时)一律是一次性的,且必须通过在信号变为活跃状态后再次调用 zx_object_wait_async 来“重新配置”文件对象。

由于 epoll 的使用必须通过重复调用 epoll_wait(不一定调用 epoll_ctl)来运行,因此对 zx_object_wait_async 的重新报警调用必须在 epoll_wait 中的某处发生。

对于由级别触发的默认轮询,在 epoll_wait 中,一旦 zx_port_wait 返回有信号的文件对象,我们便无法在返回之前调用 zx_object_wait_async,因为该对象上的信号已生效,会在端口上生成重复的数据包。因此,系统会维护一个由关卡触发的活动文件描述符列表,并在进入 zx_port_wait 状态之前对此列表中的文件描述符调用 zx_object_wait_asyncepoll_wait

对于边缘触发的轮询,在 epoll_wait 返回后,应执行非阻塞 I/O,直到返回 EWOULDBLOCK。届时,文件对象上的信号将处于无效状态。此时,应调用 epoll_wait。不过,如果在返回 EWOULDBLOCK 的 I/O 操作和调用 epoll_wait 之间文件对象上的信号变为活动状态,除非已调用 zx_object_wait_async,否则该事件将丢失。因此,在边缘触发模式下,必须在 epoll_wait 返回之前调用 zx_object_wait_async 来重新组装文件对象。这正是 ZX_WAIT_ASYNC_EDGE 的必要条件。可以在 zx_port_wait 返回和 epoll_wait 返回之间使用此标志调用 zx_object_wait_async,因为虽然此时信号有效,但 ZX_WAIT_ASYNC_EDGE 会跳过检查信号是否有效(此时信号有效),因此没有数据包立即在端口上排队。这意味着,当 I/O 发生在 EWOULDBLOCK 之前时,文件对象已由 zx_object_wait_async 监控,覆盖范围不存在间断。

实现

zx_object_wait_async 添加 ZX_WAIT_ASYNC_EDGE 选项的操作已在 fxr/438521 中实现,其在 epoll 中的使用已在 fxr/438656 中实现。

性能

性能影响可以忽略不计,因为只有额外添加了方法参数,并且对该参数的检查已添加到现有代码中。

安全注意事项

不适用

隐私注意事项

不适用

测试

添加了其他单元测试。

文档

文档已添加到实现 CL 中的 zx_object_wait_asynczx_object_wait_async

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

这似乎是实现此功能的最简单方法,并且类似于在其他操作系统中实现边缘触发的轮询的方式。

使用此标志时,应注意不要错过 I/O 事件。信号可能会在执行 I/O 之后、调用 zx_object_wait_async 之前变为活动状态,在这种情况下,可能会错过从无信号到有信号的转换。实际上,ZX_WAIT_ASYNC_EDGE 标志会立即使用;在返回 zx_port_wait 后,表示相应信号在对象上处于有效状态。这样,在对对象执行非阻塞 I/O 直到信号无效(通常直到返回 ZX_ERR_SHOULD_WAIT 之前)后,可以调用 zx_port_wait 来等待其他 I/O 准备就绪。

由于边缘触发的 epoll_wait 中的 EPOLLET 为 (1) epoll_wait (2) 非阻塞 I/O,直至 fd 尚未准备就绪 (3) epoll_wait,因此 ZX_WAIT_ASYNC_EDGE 的替代方案是,检查每个 I/O 操作,以查看是否已通过 epoll_ctl 将文件描述符添加到 epoll 文件描述符并使其带有 EPOLLET(并已停止等待准备就绪),然后重新 arm。在极少数情况下,这需要对 zxiofdio 进行大量修改。

早期技术和参考资料

此更改的目的是在 Linux 中模拟 EPOLLET 标志:

https://man7.org/linux/man-pages/man7/epoll.7.html