主要用例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
on fd
within start_monitoring
添加监听EPOLLIN
,并在 epoll 报告事件后再次停止监听EPOLLIN
.
这意味着一个read
有可用数据的数据将在 1 个系统调用内完成,但返回的读取EAGAIN
将采取at least4个系统调用(原来的read
, two EPOLL_CTL_MOD
,以及最后的read
就成功了)。
注意,上面的内容并没有计算在内epoll_wait
这也必须发生。我没有计算它,因为我采取了慷慨的假设,即其他光纤也将被同一系统调用唤醒,因此将其成本完全归因于我们的光纤是不公平的。总而言之,这个机制需要4+x次系统调用,其中x在0到1之间。
降低成本的一种方法是使用EPOLLONESHOT
。这样做可以消除fd
自动监控,将我们的成本降低至 3+x。更好,但我们还可以做得更好。
Enter EPOLLET
。以前的fd
状态可以是武装的或非武装的(即 - 下一个事件是否会触发epoll
)。此外,fd 当前可能会也可能不会(在进入点)read
)准备好数据。四个州。让我们把它们分散开来。
就绪(无论是否武装):第一次呼叫read
返回数据。 1个系统调用。该路径不会改变武装状态,就绪状态取决于我们是否读取了所有内容。
未准备好(无论是否武装):第一次呼叫read
回报EAGAIN
,从而武装FD。我们去睡觉wait_event
无需执行另一个系统调用。一旦我们醒来,我们就处于非武装模式(因为我们刚刚醒来)。因此我们不需要调用epoll_ctl
禁用对 fd 的监听。我们称之为read
返回数据。我们让这个函数要么准备好,要么没有准备好,但没有准备好。
总成本:2+x。
我们将不得不面对一次虚假唤醒fd
,作为fd
开始武装。我们的代码必须处理以下情况epoll
报告没有光纤正在侦听的 fd。在这种情况下,处理只是意味着忽略并继续前进。 FD不会再被虚假报告。