1

epoll在边沿触发模式下是一头奇怪的野兽。它要求该过程跟踪每个受监视 FD 的最后响应是什么。它要求进程无误地处理每个报告的事件(否则我们可能会认为 FD 没有报告任何内容,而实际上它被边缘触发行为静音)。

epoll边缘触发有意义的用例有哪些?

4

1 回答 1

8

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是调用insideEPOLL_CTL_MOD以添加侦听,并在 epoll 报告事件后再次停止侦听。fdstart_monitoringEPOLLINEPOLLIN

这意味着read具有可用数据的 a 将在 1 个系统调用内完成,但返回的读取EAGAIN至少需要4 个系统调用( original read、 twoEPOLL_CTL_MOD和 finalread成功)。

请注意,上述内容不包括epoll_wait也必须发生的事情。我没有计算它,因为我假设其他纤程也将被同一个系统调用唤醒,因此将其成本完全归因于我们的纤程是不公平的。总而言之,这个机制需要 4+x 个系统调用,其中 x 介于 0 和 1 之间。

降低成本的一种方法是使用EPOLLONESHOT. 这样做会fd自动消除监控,将我们的成本降低到 3+x。更好,但我们还可以做得更好。

输入EPOLLET。先前的fd状态可以是武装或非武装(即 - 下一个事件是否会触发epoll)。此外,fd 当前(在入口点read)可能有也可能没有准备好数据。四个州。让我们把它们散开。

就绪(无论是否武装):第一次调用read返回数据。1 个系统调用。这条路径不会改变武装状态,而就绪状态取决于我们是否读取了所有内容。

未准备好(无论是否武装):第一次调用read返回EAGAIN,从而武装 fd。wait_event我们无需执行另一个系统调用即可进入睡眠状态。一旦我们醒来,我们就处于非武装模式(因为我们刚刚醒来)。因此,我们不需要调用epoll_ctl来禁用对 fd 的侦听。我们调用readwhich 返回数据。我们让函数准备好或不准备好,但没有准备好。

总成本:2+x。

我们将不得不面对一个虚假的唤醒fd,因为fd开始武装。我们的代码必须处理epoll报告没有光纤正在监听的 fd 的情况。在这种情况下,处理只是意味着忽略并继续前进。FD 不会再次被虚假报告。

于 2017-10-08T17:50:36.673 回答