RFC-0009:边缘触发了 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_async
和 zx_port_wait
)已经使关卡触发的轮询成为可能。但是,边缘触发的轮询需要能够等待一个处于活跃状态的对象上的信号(通过 I/O 预计会变为非活跃状态,随后再次处于活跃状态),从而在此后续信号转换时在端口上将数据包加入队列。
这是 ZX_WAIT_ASYNC_EDGE
的意图。
设计
幸运的是,zx_object_wait_async
的 ZX_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_async
。epoll_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_async
。zx_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。在极少数情况下,这需要对 zxio
和 fdio
进行大量修改。
早期技术和参考资料
此更改的目的是在 Linux 中模拟 EPOLLET 标志:
https://man7.org/linux/man-pages/man7/epoll.7.html