RFC-0009:Edge 触发的 async_wait

RFC-0009:Edge 触发的 async_wait
状态已接受
区域
  • 内核
说明

提案是,如果指定了 ZX_WAIT_ASYNC_EDGE 标志,则无论初始信号状态如何,都会省略初始检查并将信号集添加到 DispatchObject 的兴趣列表中。在此操作模式下,其中一个信号必须从非活动状态转换为活动状态,才能将数据包加入所提供的端口的队列(可能需要在该过程中让某个信号变为非活动状态)。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)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,因为该对象上的信号处于活动状态,并且会在端口上生成重复数据包。因此,系统会维护一个活跃的级别触发文件描述符列表,并在进入 epoll_wait 之前对此列表中的文件描述符调用 zx_object_wait_async,然后再在 zx_port_wait 中等待。

对于边沿触发的轮询,在 epoll_wait 返回后,应执行非阻塞 I/O,直到返回 EWOULDBLOCK。此时,文件对象上的信号将处于无效状态。此时,应调用 epoll_wait。不过,如果在 I/O 操作返回 EWOULDBLOCK 和调用 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 监控,并且覆盖范围没有空白。

实现

fxr/438521 中已实现向 zx_object_wait_async 添加 ZX_WAIT_ASYNC_EDGE 选项,fxr/438656 中已实现在 epoll 中使用该选项。

性能

性能影响将微不足道,因为只会向现有代码中添加一个额外的方法参数和对该参数的检查。

安全注意事项

不适用

隐私注意事项

不适用

测试

添加了其他单元测试。

文档

在实现 CL 中,向 zx_object_wait_async 添加了文档。 zx_object_wait_async

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

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

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

由于边沿触发的 epoll_waitEPOLLET 的模式为 (1) epoll_wait (2) 非阻塞 I/O,直到 fd 不就绪 (3) 再次 epoll_wait,因此 ZX_WAIT_ASYNC_EDGE 的替代方案是在每次 I/O 操作中执行检查,以查看文件描述符是否已通过 epoll_ctlEPOLLET 添加到 epoll 文件描述符(并且已停止就绪),并重新启用等待。这需要对 zxiofdio 进行大量修改,以适应某种比较少见的用例。

在先技术和参考文档

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

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