EPOLL(7)

ИМЯ
epoll - средство уведомления о событии ввода-вывода

ОБЗОР
#include <sys/epoll.h>

ОПИСАНИЕ
Программный интерфейс epoll выполняется схожую с poll(2) задачу: следит за несколькими файловыми дескрипторами и ждёт, когда станет возможен ввод-вывод для одного из них. Программный интерфейс epoll можно использовать либо в режиме edge-triggered, либо в level-triggered и применять для слежения за достаточно большим количеством файловых дескрипторов.

Центральным элементом программного интерфейса epoll является экземпляр epoll — структура данных ядра, которая с точки зрения пользовательского пространства может рассматриваться как контейнер с двумя списками:

*   Список interest (иногда также называемый набором epoll): набор файловых дескрипторов, которые зарегистрировал процесс для слежения.

*   Список ready: набор файловых дескрипторов, которые «готовы» для проведения операций ввода-вывода. Список ready — это часть набора (точнее, набор ссылок) файловых дескрипторов из списка interest, он динамически заполняется ядром в результате действий ввода-вывода с этими файловыми дескрипторами.

Для создания и управления экземпляром epoll служат следующие системные вызовы:

*   Вызов epoll_create(2) создаёт экземпляр новый epoll и возвращает файловый дескриптор, указывающий на этот экземпляр (более новый epoll_create1(2) расширяет возможности epoll_create(2)).

*   Затем с помощью epoll_ctl(2) регистрируются интересующие файловые дескрипторы, который добавляет их в список interest экземпляра epoll.

*   Вызов epoll_wait(2) ждёт наступления событий ввода-вывода, блокируя вызывающую нить, если события пока недоступны (данный системный вызов можно рассматривать как выборщике элементов из списка готовности экземпляра epoll).

  Режимы level-triggered и edge-triggered
Существует два режима выдачи событий epoll: edge-triggered (ET) и level-triggered (LT). Разницу между ними можно описать так. Предположим, что реализован следующий сценарий событий:

1.  Файловый дескриптор, представляющий читающую сторону канала (rfd), регистрируется в экземпляре epoll.

2.  Пишущая сторона канала записывает 2 КБ данных на записываемой стороне канала.

3.  Вызов epoll_wait(2) завершается и возвращает rfd как готовый файловый дескриптор.

4.  Читающая сторона канала считывает 1 КБ данных из rfd.

5.  Вызов epoll_wait(2) завершается.

Если файловый дескриптор rfd добавлен к экземпляру epoll с указанным флагом EPOLLET (edge-triggered), то вызов epoll_wait(2) на шаге 5, вероятно, повиснет, несмотря на имеющие данные в буфере ввода; в это же время удалённая сторона может ожидать подтверждения приёма уже отправленных данных. Причиной этого является то, что в режиме edge-triggered события доставляются только когда происходит изменение состояния отслеживаемого файлового дескриптора. Поэтому в шаге 5 вызывающий может бесконечно ждать появления данных, хотя они уже есть в буфере ввода. В приведённом выше примере событие для rfd будет сгенерировано из-за операции записи, сделанной в шаге 2, и это событие будет обработано в шаге 3. Так как операция в шаге 4, не прочитала все данные из буфера, вызов epoll_wait(2) в шаге 5 может заблокироваться навсегда.

Приложение, которое применяет флаг EPOLLET, должно использовать неблокирующие файловые дескрипторы, чтобы избежать приостановки задания, обрабатывающего множество файловых дескрипторов, из-за блокировок чтения или записи. Предлагаемый способ использования epoll с интерфейсом Edge Triggered (EPOLLET):

    i   неблокирующие файловые дескрипторы; и

    ii  ожидание события только после того, как read(2) или write(2) возвратят EAGAIN.

Напротив, при использовании интерфейса level-triggered (по умолчанию, если не указан EPOLLET) epoll проще и быстрее poll(2), и может быть использован везде, где используется последний, так как имеет ту же семантику.

Так как даже с edge-triggered epoll при получении нескольких порций данных могут генерироваться множественные события, вызывающий может задать флаг EPOLLONESHOT, который указывает epoll отключить связанный файловый дескриптор после приёма события с помощью epoll_wait(2). Если указан флаг EPOLLONESHOT, то вызывающий должен переустановить файловый дескриптор с помощью epoll_ctl(2) с флагом EPOLL_CTL_MOD.

Если несколько нитей (или процессов, если дочерние процессы унаследовали файловый дескриптор epoll при fork(2)) блокируются в ожидании epoll_wait(2) одного и того же файлового дескриптора и файловый дескриптор в списке interest, помеченный для уведомления edge-triggered (EPOLLET), становится готовым, то только одна из нитей (или процессов) пробуждается из epoll_wait(2). Такая полезная оптимизация в некоторых случаях помогает избежать «лавины» пробуждений.

  Взаимодействие с autosleep
Если система в режиме autosleep посредством /sys/power/autosleep и происходит событие, которое пробуждает устройство, то драйвер устройства держит устройство проснувшимся только, пока событие ставится в очередь. Чтобы устройство не заснуло пока не обработает событие, необходимо использовать флаг epoll_ctl(2) EPOLLWAKEUP.

Флаг EPOLLWAKEUP задаётся в поле events для struct epoll_event; система будет оставаться разбуженной с момента когда событие поступает в очередь, пока не закончится работа вызова epoll_wait(2), возвращающий событие, и до последующего вызова epoll_wait(2). Если событие должно держать систему разбуженной дольше, то нужно применить отдельный wake_lock перед вторым вызовом epoll_wait(2).

  Интерфейсы /proc
Для ограничения потребления epoll памяти ядра, можно использовать следующие интерфейсы:

/proc/sys/fs/epoll/max_user_watches (начиная с Linux 2.6.28)
    Задаёт ограничение на общее количество файловых дескрипторов, которые пользователь может зарегистрировать во всех экземплярах epoll в системе. Ограничение привязывается к реальному идентификатору пользователя. Каждый зарезервированный файловый дескриптор занимает, приблизительно, 90 байт в 32-битном ядре, и, приблизительно, 160 байт в 64-битном ядре. В настоящее время, значение по умолчанию для max_user_watches равно 1/25 (4%) доступной памяти ядра (low memory), поделённое на значение размера дескриптора в байтах.

  Примеры использования
При применении epoll с интерфейсом level-triggered он имеет ту же семантику что и poll(2), а при edge-triggered требует больших проверок для избежания зависаний приложения в событийном цикле. В этом примере, слушающим является неблокирующий сокет, для которого был вызван listen(2). Функция do_use_fd() использует новый готовый файловый дескриптор до тех пор, пока не возвратится EAGAIN от read(2) или write(2). Приложение на основе машины состояний должно после получения EAGAIN записать своё текущее состояние так, чтобы последующий вызов do_use_fd() продолжил выполнять read(2) или write(2) с места остановки.

    #define MAX_EVENTS 10
    struct epoll_event ev, events[MAX_EVENTS];
    int listen_sock, conn_sock, nfds, epollfd;
    /* Код для настройки слушающего сокета, 'listen_sock',
       (socket(), bind(), listen()) не показаны */
    epollfd = epoll_create1(0);
    if (epollfd == -1) {
        perror("epoll_create1");
        exit(EXIT_FAILURE);
    }
    ev.events = EPOLLIN;
    ev.data.fd = listen_sock;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
        perror("epoll_ctl: listen_sock");
        exit(EXIT_FAILURE);
    }
    for (;;) {
        nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
        if (nfds == -1) {
            perror("epoll_wait");
            exit(EXIT_FAILURE);
        }
        for (n = 0; n < nfds; ++n) {
            if (events[n].data.fd == listen_sock) {
                conn_sock = accept(listen_sock,
                                   (struct sockaddr *) &addr, &addrlen);
                if (conn_sock == -1) {
                    perror("accept");
                    exit(EXIT_FAILURE);
                }
                setnonblocking(conn_sock);
                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = conn_sock;
                if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
                            &ev) == -1) {
                    perror("epoll_ctl: conn_sock");
                    exit(EXIT_FAILURE);
                }
            } else {
                do_use_fd(events[n].data.fd);
            }
        }
    }

При использовании интерфейса edge-triggered для большей производительности можно однократно добавить файловый дескриптор внутрь интерфейса epoll (EPOLL_CTL_ADD), указав (EPOLLIN|EPOLLOUT). Это позволит вам избежать постоянного переключения между EPOLLIN и EPOLLOUT, вызывающими epoll_ctl(2) c EPOLL_CTL_MOD.

  Вопросы и ответы

0.  По какому ключу различать зарегистрированные файловые дескрипторы в списке interest?

    Уникальной комбинацией является номер файлового дескриптора и описание открытого файла (так называемый «описатель открытого файла» — внутреннее представление открытого файла в ядре).

1.  Что случится, если зарегистрировать один файловый дескриптор в экземпляре epoll дважды?

    Вероятно, вы получите EEXIST. Однако возможно добавить дубликат файлового дескриптора (dup(2), dup2(2), fcntl(2) F_DUPFD) в тот же экземпляр epoll. Это может быть полезно для фильтрующих событий, если дубликаты файловых дескрипторов регистрируются с разными масками events.

2.  Могут ли два экземпляра epoll ожидать один файловый дескриптор? Если да, то сообщаются ли события в оба файловых дескриптора epoll?

    Да, и события будут доходить в оба. Однако, чтобы сделать это правильно, нужна внимательность к деталям.

3.  Могут ли операции poll/epoll/select применяться к самому файловому дескриптору epoll?

    Да. Если файловый дескриптор epoll имеет ожидающие события, то он будет помечен как доступный для чтения.

4.  Что случится, если попытаться поместить файловый дескриптор epoll в свой собственный набор файловых дескрипторов?

    Вызов epoll_ctl(2) завершается ошибкой (EINVAL). Однако вы можете добавить файловый дескриптор epoll внутрь другого набора файлового дескриптора epoll.

5.  Можно ли отправить файловый дескриптор epoll через доменный сокет UNIX другому процессу?

    Да, но это не имеет смысла, так как принимающий процесс не имеет копий файловых дескрипторов в списке interest.

6.  Приводит ли закрытие файлового дескриптора к его удалению из всех списков interest epoll?

    Да, но учтите следующий момент. Файловый дескриптор является ссылкой на открытое файловое описание (смотрите open(2)). При создании дубля файлового дескриптора с помощью dup(2), dup2(2), fcntl(2) F_DUPFD или fork(2) созданный новый файловый дескриптор указывает на то же открытое файловое описание. Открытое файловое описание продолжает существовать до тех пор, пока все указывающие на него файловые дескрипторы не будут закрыты.

    Файловый дескриптор удаляется из списка interest только после того, как будут закрыты все файловые дескрипторы, ссылающиеся на открытое файловое описание. Это означает, что даже после закрытия файлового дескриптора, являющегося частью списка interest, могут поступать события от файлового дескриптора, если остались открытыми другие файловые дескрипторы, ссылающиеся на тоже файловое описание. Чтобы такого не случалось, файловый дескриптор должен быть удалён из списка interest явным образом (с помощью epoll_ctl(2) EPOLL_CTL_DEL) до создания его дубликата. Или же приложение может проверить,что закрыты все файловые дескрипторы (что может быть трудно, если дубли файлового дескриптора неявно создавались где-то в библиотечных функциях с помощью dup(2) или fork(2)).

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

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

8.  Влияет ли операция над файловым дескриптором на уже собранные, но пока ещё не сообщенные события?

    Вы можете выполнить две операции на существующем файловом дескрипторе. Удаление в этом случае бессмысленно. Изменение приведёт к повторному чтению доступного ввода/вывода.

9.  Должен ли я читать/записывать файловый дескриптор до пор пока, не получу EAGAIN при использовании флага EPOLLET (поведение edge-triggered)?

    Получение события от epoll_wait(2) должно сообщить вам, что файловый дескриптор готов для запрошенной операции ввода/вывода. Вы должны предполагать, что он готов до тех пор, пока вы не получите следующий EAGAIN от (неблокирующего) чтения/записи. Когда и как вы будете использовать файловый дескриптор — полностью зависит от вас.

    Для пакетных/метко ориентированных файлов (например, датаграмных сокетов, терминал в каноническом режиме) единственным способом обнаружить конец чтения/записи пространства ввода-вывода — это продолжать чтение/записи до получения EAGAIN.

    Для потокоориентированных файлов (например, каналы, FIFO, потоковые сокеты) условие, при которых чтение/запись пространства ввода/вывода закончилось, может быть определено проверкой количества считанных/записанных данных из/в целевого файлового дескриптора. Например, если вы вызвали read(2) для чтения определённого количества данных и read(2) вернул меньшее количество байтов, то можно быть уверенным, что пространство чтения ввода/вывода этого файлового дескриптора закончилось. То же самое справедливо для записи посредством write(2) (не используйте последнее, если вы не можете гарантировать, что отслеживаемый файловый дескриптор всегда ссылается на потокоориентированный файл).

  Возможные ловушки и способы их обхода

o Информационный голод (edge-triggered)

Если существует большое пространство ввода/вывода, то возможно, что пока вы его читаете, другие файлы не будут обрабатываться и возникнет недостаток данных (этого, обычно, не происходит с epoll).

Решением будет поддержка списка готовности и маркировка файлового дескриптора как готового в связанной с ним структуре данных, тем самым позволяя приложению запоминать какие файлы требуют обработки, но всё ещё не обработанных среди уже готовых файлов. Это также поддерживает игнорирование последующих событий готовности файловых дескрипторов, получаемых вами.

o Если использовать кэш событий…

Если вы используете кэш событий или храните все файловые дескрипторы, возвращённые от epoll_wait(2), то убедитесь, что вы обеспечили способ его динамического закрытия (например, вызванное обработкой предыдущего события). Предположим, что вы получили 100 событий от epoll_wait(2), и что в событии №47 некоторое условие определяет, что событие №13 должно быть закрыто. Если вы удалите структуру и выполните close(2) файлового дескриптора для события №13, то кэш событий всё ещё может сообщать о том, что есть ожидаемые события для этого файлового дескриптора, что приводит к путнице.

Одним из решений будет вызов, во время обработки события №47, epoll_ctl(EPOLL_CTL_DEL) для удаления файлового дескриптора 13 и вызов close(2), а затем маркировка связанной с ним структуры данных как удалённой и связки его со списком очистки. Если при пакетной обработке найдется другое событие для файлового дескриптора 13, то обнаружится, что файловый дескриптор уже был удалён и конфликтов не будет.

ВЕРСИИ
Программный интерфейс epoll был добавлен в ядро Linux версии 2.5.44. Поддержка в glibc доступна с версии 2.3.2.

СООТВЕТСТВИЕ СТАНДАРТАМ
Программный интерфейс epoll есть только в Linux. В некоторых других системах есть подобные механизмы, например, в FreeBSD есть kqueue, а в Solaris — /dev/poll.

ЗАМЕЧАНИЯ
Набор файловых дескрипторов, которые отслеживаются через файловый дескриптор epoll, можно найти в записи для файлового дескриптора epoll в каталоге процесса /proc/[pid]/fdinfo. Подробности смотрите в proc(5).

Вызов The kcmp(2) с операцией KCMP_EPOLL_TFD можно использовать для проверки, что файловый дескриптор присутствует в экземпляре epoll.

СМОТРИТЕ ТАКЖЕ
epoll_create(2), epoll_create1(2), epoll_ctl(2), epoll_wait(2), poll(2), select(2)