RFC-0009:Edge 触发的 async_wait

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

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

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

摘要

等待对象上的信号被断言通常是电平触发的,并且在 zx_object_wait_async 的开头会进行检查,以防信号已经处于有效状态,在这种情况下,数据包会立即发送到端口。此 RFC 涉及向 zx_object_wait_asyncZX_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_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这需要对 zxiofdio 进行相当大的修改,但用例却相对较少。

在先技术和参考资料

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

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