Provided by: manpages-ru_4.18.1-1_all bug

ИМЯ

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

СИНТАКСИС

       #include <sys/epoll.h>

ОПИСАНИЕ

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

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

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

       •  The ready list: the set of file descriptors that are "ready" for I/O.  The  ready  list
          is a subset of (or, more precisely, a set of references to) the file descriptors in the
          interest list.  The ready list is dynamically populated by the kernel as  a  result  of
          I/O activity on those file descriptors.

       Для создания и управления экземпляром 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):

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

       (2)  ожидание события только после того, как 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.

       If  multiple  threads  (or  processes,  if  child  processes have inherited the epoll file
       descriptor across fork(2))  are blocked in epoll_wait(2) waiting on the  same  epoll  file
       descriptor  and  a  file descriptor in the interest list that is marked for edge-triggered
       (EPOLLET)  notification becomes ready, just one of the threads (or  processes)  is  awoken
       from  epoll_wait(2).   This  provides a useful optimization for avoiding "thundering herd"
       wake-ups in some scenarios.

   Взаимодействие с 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;

           /* Code to set up listening socket, 'listen_sock',
              (socket(), bind(), listen()) omitted. */

           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.

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

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

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

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

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

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

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

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

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

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

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

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

       •  Приводит ли закрытие файлового дескриптора к его  удалению  из  всех  списков  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)).

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

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

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

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

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

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

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

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

   Возможные ловушки и способы их обходаStarvation (edge-triggered)

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

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

       •  If using an event cache...

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

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

ВЕРСИИ

       The epoll API was introduced in Linux kernel 2.5.44.  Support was added in glibc 2.3.2.

СТАНДАРТЫ

       Программный интерфейс epoll есть только в Linux. В некоторых других системах есть подобные
       механизмы, например, в FreeBSD есть kqueue, а в Solaris — /dev/poll.

ЗАМЕЧАНИЯ

       The  set  of  file descriptors that is being monitored via an epoll file descriptor can be
       viewed via the entry for the epoll  file  descriptor  in  the  process's  /proc/pid/fdinfo
       directory.  See proc(5)  for further details.

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

СМ. ТАКЖЕ

       epoll_create(2), epoll_create1(2), epoll_ctl(2), epoll_wait(2), poll(2), select(2)

ПЕРЕВОД

       Русский   перевод   этой    страницы    руководства    был    сделан    Azamat    Hackimov
       <azamat.hackimov@gmail.com>,    Yuri    Kozlov    <yuray@komyakino.ru>   и   Иван   Павлов
       <pavia00@gmail.com>

       Этот  перевод  является  бесплатной  документацией;  прочитайте  Стандартную  общественную
       лицензию GNU версии 3 ⟨https://www.gnu.org/licenses/gpl-3.0.html⟩ или более позднюю, чтобы
       узнать об условиях авторского права. Мы не несем НИКАКОЙ ОТВЕТСТВЕННОСТИ.

       Если вы обнаружите ошибки в переводе  этой  страницы  руководства,  пожалуйста,  отправьте
       электронное письмо на ⟨man-pages-ru-talks@lists.sourceforge.net⟩.