Почему 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_wait
s на другой розетке.
Когда я запускаю программу, она возвращается при первом вызове 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;
}