【问题标题】:Why EPOLLOUT changes how EPOLLIN is handled?为什么 EPOLLOUT 会改变 EPOLLIN 的处理方式?
【发布时间】:2016-07-18 02:06:06
【问题描述】:

文档不清楚事件是否被合并,我的测试表明它们在某些情况下但并非总是如此。

考虑man 7 epoll

由于即使使用边缘触发的 epoll,在接收到多个数据块时也可以生成多个事件,调用者已经 指定 EPOLLONESHOT 标志的选项...

以及问答部分:

Q7 如果 epoll_wait(2) 调用之间发生多个事件,是合并还是单独报告?

A7 它们将被合并。

我假设手册中的第一条语句意味着在从套接字读取、数据包到达、您读取它、然后另一个数据包到达等情况下,您可以接收多个 EPOLLIN 事件。 Q&A 部分的答案是讨论不同的事件,如 EPOLLIN 和 EPOLLOUT。如果我错了,请纠正我。

为了更好地理解 epoll 是如何工作的,我正在玩一些代码,并且它似乎在基于是否设置了另一个事件的情况下对相同类型的事件表现不同。更准确地说,如果我只等待 EPOLLIN,则多个输入会生成一个事件,但如果我同时等待 EPOLLIN 和 EPOLLOUT,则多个输入会生成多个事件。

这是我用来测试的代码:

#include <stdio.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
  struct epoll_event ev = {EPOLLIN|EPOLLOUT|EPOLLET};
  int epoll = epoll_create1(0);
  epoll_ctl(epoll, EPOLL_CTL_ADD, 0, &ev);

  //async stdin
  int flags = fcntl(0, F_GETFL);
  flags |= O_NONBLOCK;
  fcntl(0, F_SETFL, flags);

  while(1){
    struct epoll_event events[64];
    int n = epoll_wait(epoll, events, 64, -1);

    printf("Event count: %d\n", n);

    if(events[0].events == EPOLLIN)
      printf("EPOLLIN only\n\n");

    else
    if(events[0].events == (EPOLLIN|EPOLLOUT))
      printf("EPOLLIN and EPOLLOUT\n\n");

    else
      printf("EPOLLOUT only\n\n");

    char buffer[256];
    read(0, buffer, 256);

    sleep(1);
  }
  return 0;
}

按回车后的输出显示EPOLLIN和EPOLLOUT都收到了,这个信息出现的次数与按回车的次数一样多,然后显示只有EPOLLOUT正在生成。

但是如果你在编译程序时不带EPOLLOUT标志并多次按return,那么单个事件将只报告一次。

如果我删除 read 调用,则在设置 EPOLLOUT 时会继续报告 EPOLLIN,但在仅设置 EPOLLIN 时不会报告。

行为取决于它正在等待的事件还是我的测试代码有问题?如果它是依赖的,我可以放心它将来不会改变吗?

【问题讨论】:

  • 简而言之,您通过在只读文件描述符上请求EPOLLOUT 通知来滥用epoll API。我已经提供了完整的答案。

标签: c linux epoll epollet


【解决方案1】:

我相信您正在观察未定义行为的影响,因为您在滥用 API。

具体来说,您将STDIN_FILENO(即0)传递给epoll_ctl,并要求在只读文件描述符上等待EPOLLOUT。可能发生的情况是操作系统试图告诉您文件描述符的写入方向有问题。

另外,当使用边沿触发模式时,您应该继续您的 I/O,直到看到 EAGAIN。当操作不再阻塞时,epoll_wait 调用返回。

我修改了您的程序以改用套接字,并从套接字读取直到EAGAIN,它的行为与我预期的一样。

在我的版本中,我创建了一个套接字对和一个从STDIN_FILENO 读取并写入其中一个套接字的线程。 main 主体循环然后 epoll_waits 在另一个套接字上。

当我启动程序时,它会在第一次调用 epoll_wait 时返回以报告可写,但在下一次迭代时会阻塞:

Event count: 1
EPOLLOUT only

当我输入输入时,它报告可读和可写,然后在下一次迭代中阻塞epoll_wait,正如预期的那样:

asdf
Event count: 1
EPOLLIN and EPOLLOUT

我使用的代码如下。一、线程:

static void * iothread (void *svp) {
    int *sv = svp;
    char buf[256];
    ssize_t r;
again:
    while ((r = read(0, buf, sizeof(buf))) > 0) {
        ssize_t n = r;
        const char *p = buf;
        while (n > 0) {
            r = write(sv[1], p, n);
            if (r < 0) {
                if (errno == EINTR) continue;
                break;
            }
            n -= r;
            p += r;
        }
        if (n > 0) break;
    }
    if (r < 0 && errno == EINTR) {
        goto again;
    }
    close(sv[1]);
    return NULL;
}

然后,main 正文:

int main(int argc, char* argv[]) {
  int sv[2];
  struct epoll_event ev = {EPOLLIN | EPOLLOUT | EPOLLET};
  int epoll = epoll_create1(0);
  pthread_t t;

  socketpair(AF_LOCAL, SOCK_STREAM, 0, sv);
  pthread_create(&t, NULL, iothread, sv);
  epoll_ctl(epoll, EPOLL_CTL_ADD, sv[0], &ev);
  while(1){
    struct epoll_event events[64];
    int n = epoll_wait(epoll, events, 64, -1);

    printf("Event count: %d\n", n);

    if(events[0].events == EPOLLIN)
      printf("EPOLLIN only\n\n");
    else
    if(events[0].events == (EPOLLIN|EPOLLOUT))
      printf("EPOLLIN and EPOLLOUT\n\n");
    else
      printf("EPOLLOUT only\n\n");

    char buffer[256];
    ssize_t r;
again:
    r = recv(sv[0], buffer, 256, MSG_DONTWAIT);
    if (r > 0) goto again;
    if (r < 0 && errno == EAGAIN) {
        sleep(1);
        continue;
    }
    break;
  }
  return 0;
}

【讨论】:

    猜你喜欢
    • 2015-10-04
    • 1970-01-01
    • 1970-01-01
    • 2015-02-18
    • 1970-01-01
    • 1970-01-01
    • 2019-01-21
    • 2015-08-20
    • 1970-01-01
    相关资源
    最近更新 更多