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