我对基于事件的编程相当陌生。我正在试验epoll
的edge-mode,它显然只信号已经准备好读/写的文件(而不是信号所有准备好的文件的电平模式,不管是否已经准备好了,或者刚刚准备好了)。
我不清楚的是:在边缘模式下,我是否被告知在我没有epoll_wait
时发生的准备事件?
为了说明我为什么要问这个问题,请考虑以下场景:
- 有10个非阻塞套接字连接
- 配置
epoll_ctl
在套接字准备读取时响应,在edge-mode + oneshot:EPOLLET | EPOLLONESHOT | EPOLLIN
-
epoll_wait
for something happened(报告最多10个事件) - linux唤醒我的进程并报告套接字#1和#2准备就绪
- I
read
和处理数据套接字#1(直到E_AGAIN
) - I
read
和处理数据套接字#2(直到E_AGAIN
) - 当我做时,套接字S接收数据
- 我处理了所有的事件,所以我用
epoll_ctl
在EPOLL_CTL_MOD
模式下重新武装触发的文件,因为一次性 - 我的循环回到
epoll_wait
的下一批事件
好,那么最后一个epoll_wait
总是被通知插座S就绪吗?如果S是#1(即它没有重新武装)事件?
我正在试验epoll的边缘模式,显然只有信号已经准备好读/写的文件(与级别模式相反)哪个信号表示所有就绪文件,不管是否存在已经准备好,或者刚刚准备好)
首先让我们对系统有一个清晰的认识,你需要一个关于系统如何工作的准确的心理模型。你对epoll(7)
的看法并不准确。
边缘触发和水平触发之间的区别在于对事件的定义。前者为文件描述符上订阅的每个操作生成一个事件;一旦您使用了该事件,它就消失了——即使您没有使用生成该事件的所有数据。当然,后者会一遍又一遍地生成相同的事件,直到您消耗了生成该事件的所有数据。
这里有一个例子,把这些概念付诸行动,公然偷自man 7 epoll
:
代表管道读端(rfd)的文件描述符在epoll实例上注册。
管道写入器在管道的写端写入2kb的数据
调用epoll_wait(2)将返回rfd作为一个就绪文件描述符。
管道读取器从rfd读取1kb的数据
调用epoll_wait(2)完成。
如果rfd文件描述符已经添加到epoll接口,使用epolllet(边缘触发)标志,调用epoll_wait(2)完成步骤5可能会挂起,尽管可用数据仍然存在文件输入缓冲区;与此同时,远端对等端可能正在等待基于它已经发送的数据的响应。这样做的原因是边缘触发模式仅在发生更改时才传递事件被监视的文件描述符。因此,在步骤5中,调用者可能会结束等待一些已经存在于输入缓冲区中的数据。在上面的示例中,将生成rfd上的事件,因为写入在2中完成,事件在3中消耗。自从阅读在4中完成的操作不会消耗整个缓冲区数据,调用在步骤5中执行的epoll_wait(2)可能会无限期阻塞。
简而言之,根本区别在于"事件"的定义:边缘触发将事件视为单个单元,您可以一次性使用;Level-triggered将事件的消费定义为等同于消费属于该事件的所有数据。
现在,让我们来回答你的具体问题。
在edge模式下,我是否被告知准备就绪事件,当我不是epoll_waiting
是的,你是。在内部,内核将发生在每个文件描述符上的有趣事件排成队列。它们将在下次调用epoll_wait(2)
时返回,因此您可以放心,不会丢失事件。嗯,如果有其他事件挂起,并且传递给epoll_wait(2)
的事件缓冲区不能容纳所有事件,那么可能不完全是在下一次调用时,但关键是,最终这些事件将被报告。
还没有重新武装的一次性文件上的事件怎么办?
同样,您永远不会丢失事件。如果文件描述符还没有被重新武装,如果出现任何有趣的事件,它就会在内存中排队,直到文件描述符被重新武装。一旦它被重新武装,任何挂起的事件——包括在描述符被重新武装之前发生的事件——将在下一次对epoll_wait(2)
的调用中报告(同样,可能不完全是下一个,但它们将被报告)。换句话说,EPOLLONESHOT
不禁用事件监视,它只是暂时禁用事件通知。
考虑到我上面所说的,现在应该很清楚了:是的,它会。你不会输的。Epoll提供了强有力的保证,它很棒。它也是线程安全的,您可以在不同的线程中等待相同的epoll fd并并发地更新事件订阅。Epoll非常强大,非常值得花时间去学习它!好的,那么最后一个epoll_wait是否总是被通知套接字年代?如果S是#1事件(即它没有重新武装)?