我正在尝试使用 epoll 的边缘模式,它显然只表示已经准备好读/写的文件(而不是表示所有准备好的文件的级别模式,无论是否已经准备好或刚刚准备好)
首先让我们对系统有一个清晰的认识,您需要一个关于系统如何工作的准确心智模型。你的观点epoll(7)
并不准确。
边沿触发和电平触发之间的区别在于对事件的确切定义。前者为文件描述符上已订阅的每个动作生成一个事件;一旦你消费了这个事件,它就消失了——即使你没有消费产生这样一个事件的所有数据。OTOH,后者不断生成相同的事件,直到您使用所有生成事件的数据。
这是一个将这些概念付诸实践的示例,公然从以下内容中窃取man 7 epoll
:
代表管道读取端的文件描述符(rfd)注册在 epoll 实例上。
管道写入器在管道的写入端写入 2 kB 的数据。
调用 epoll_wait(2) 将返回 rfd 作为准备好的文件描述符。
管道阅读器从 rfd 读取 1 kB 的数据。
完成对 epoll_wait(2) 的调用。
如果已使用 EPOLLET(边缘触发)标志将 rfd 文件描述符添加到 epoll 接口,则在步骤 5 中完成的对 epoll_wait(2) 的调用可能会挂起,尽管文件输入缓冲区中仍然存在可用数据;同时,远程对等点可能期待基于它已经发送的数据的响应。这样做的原因是边缘触发模式仅在受监视的文件描述符发生更改时才传递事件。因此,在第 5 步中,调用者可能最终会等待输入缓冲区中已经存在的一些数据。在上面的例子中,由于 2 中的 write done 会在 rfd 上产生一个事件,并且该事件在 3 中被消费。由于 4 中完成的读操作不会消耗整个缓冲区数据,所以对 epoll_wait(2) 的调用完成在第 5 步中可能会无限期阻塞。
简而言之,根本区别在于“事件”的定义:边缘触发将事件视为您消费一次的单个单元;level-triggered 将事件的消耗定义为等同于消耗属于该事件的所有数据。
现在,让我们解决您的具体问题。
在边缘模式下,我是否知道在我不是 epoll_waiting 时发生的准备就绪事件
是的,你是。在内部,内核将每个文件描述符上发生的有趣事件排队。它们会在下次调用 时返回epoll_wait(2)
,因此您可以放心,您不会丢失事件。好吧,如果还有其他未决事件并且传递给的事件缓冲区epoll_wait(2)
无法容纳所有事件,则可能不完全在下一次调用中,但关键是,最终这些事件将被报告。
尚未重新武装的一次性文件上的事件呢?
同样,您永远不会丢失事件。如果文件描述符还没有被重新配置,那么如果出现任何有趣的事件,它就会简单地在内存中排队,直到文件描述符被重新配置。一旦重新启动,任何未决事件(包括在描述符重新启动之前发生的事件)都将在下一次调用中报告(epoll_wait(2)
同样,可能不完全是下一次,但它们会被报告)。换句话说,不禁用事件监控,它只是暂时EPOLLONESHOT
禁用事件通知。
好的,那么最后的 epoll_wait 是否会一直被通知 socket S 的就绪状态呢?如果 S 是 #1 的事件(即它没有重新武装)?
鉴于我上面所说的,现在应该很清楚:是的,它会的。你不会失去任何事件。epoll 提供了强有力的保证,非常棒。它也是线程安全的,您可以在不同线程中等待同一个 epoll fd 并同时更新事件订阅。epoll 非常强大,值得花时间学习一下!