epoll
是一个奇怪的野兽。它要求该过程跟踪每个受监视FD的最后响应。它要求流程尽可能地处理报告的每个事件(否则我们可能会认为FD没有报告任何事情,而事实上,它被边缘触发行为静音)。
何时以这种方式使用epoll
是有意义的?
答案 0 :(得分:4)
我所知道的EPOLLET
的主要用例是使用微线程。
回顾一下 - 用户空间正在微线程之间进行上下文切换(我将称之为“光纤”,因为它更短),这取决于要处理的内容的可用性。这也称为“协作多任务”。
文件描述符的基本处理是通过包装相关的IO函数,如下所示:
ssize_t read(int fd, void *buffer, size_t length) {
// fd should already be in O_NONBLOCK mode
while(true) {
ssize_t result = ::read(fd, buffer, length); // The real read
if( result!=-1 || (errno!=EAGAIN && errno!=EWOULDBLOCK) )
return result;
start_monitoring(fd, READ);
wait_event();
}
}
start_monitoring
是一个函数,可确保监视fd
的读取可用性。 wait_event
执行上下文切换,直到调度程序重新唤醒此光纤,因为fd
现在已准备好读取数据。
通过epoll
实施此操作的常用方法是在EPOLL_CTL_MOD
内的fd
上致电start_monitoring
以添加对EPOLLIN
的监听,并在epoll之后再次已报告该活动停止收听EPOLLIN
。
这意味着具有可用数据的read
将在1次系统调用中完成,但返回EAGAIN
的读取将至少 4次系统调用(原始{ {1}},两个read
,以及成功的最终EPOLL_CTL_MOD
。
请注意,上述内容不计入必须发生的read
。我不算数,因为我采取了慷慨的假设,其他纤维也将被同一系统调用唤醒,因此将其成本完全归因于我们的光纤是不公平的。总而言之,这种机制需要4 + x系统调用,其中x介于0和1之间。
降低成本的一种方法是使用epoll_wait
。这样做会自动从监控中删除EPOLLONESHOT
,从而将成本降低到3 + x。更好,但我们可以做得更好。
输入fd
。之前的EPOLLET
状态可以是武装的或非武装的(即 - 下一个事件是否会触发fd
)。此外,fd当前可能会或可能不会(在epoll
的入口处)准备好数据。四个州。让我们把它们展开。
就绪(无论是否武装):第一次呼叫read
将返回数据。 1系统调用。这条路径不会改变武装状态,准备状态取决于我们是否阅读了所有内容。
未准备好(无论是否武装):第一次呼叫read
会返回read
,从而武装fd。我们在EAGAIN
进入睡眠状态而无需执行另一个系统调用。一旦我们醒来,我们就处于非武装模式(因为我们刚刚醒来)。因此,我们不需要调用wait_event
来禁用对fd的监听。我们调用epoll_ctl
来返回数据。我们让这个功能准备好或不准备,但是没有武装。
总费用:2 + x。
当read
开始武装时,每fd
我们将面临一次虚假的唤醒。我们的代码必须处理fd
报告没有光纤正在侦听的fd的情况。在这种情况下,处理只是意味着忽略并继续前进。 FD不会再被虚假报道。