Почему EPOLLOUT меняет способ обработки EPOLLIN?

В документации неясно, объединяются ли события или нет, и мои тесты показывают, что они есть в некоторых случаях, но не всегда.

Рассматривать man 7 epoll:

Так как даже при работе с epoll-триггерами, инициируемыми ребром, при получении нескольких порций данных может быть сгенерировано несколько событий, вызывающая сторона имеет возможность указать флаг EPOLLONESHOT...

и раздел вопросов и ответов:

В7 Если между вызовами epoll_wait(2) происходит более одного события, они объединяются или сообщаются отдельно?

A7 Они будут объединены.

Я предполагаю, что первое утверждение из руководства означает, что вы можете получить более одного события EPOLLIN в ситуациях, таких как чтение из сокета, пакет приходит, вы читаете его, затем приходит другой пакет. И ответ из раздела "Вопросы и ответы" говорит о различных событиях, таких как 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 Вызов EPOLLIN продолжает поступать при установке EPOLLOUT, но не при установке только EPOLLIN.

Зависит ли поведение от ожидаемых событий или что-то не так с моим тестовым кодом? Если это зависит, могу ли я быть уверен, что это не изменится в будущем?

0 ответов

Я считаю, что вы наблюдаете последствия неопределенного поведения, потому что злоупотребляете API.

В частности, вы проходите STDIN_FILENO (т.е. 0) к epoll_ctl и просят подождать EPOLLOUTна дескрипторе файла, который доступен только для чтения. Скорее всего, происходит то, что операционная система пытается сказать вам, что что-то не так с направлением записи файлового дескриптора.

Кроме того, при использовании режима запуска по фронту вы должны продолжать ввод-вывод, пока не увидите 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;
}
Другие вопросы по тегам