Provided by: manpages-ru_4.27.0-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, то обнаружится, что файловый дескриптор  уже  был  удалён  и  конфликтов  не
          будет.

ВЕРСИИ

       Some  other  systems  provide  similar  mechanisms;  for  example,  FreeBSD  has  kqueue, and Solaris has
       /dev/poll.

СТАНДАРТЫ

       Linux.

ИСТОРИЯ

       Linux 2.5.44.  glibc 2.3.2.

ПРИМЕЧАНИЯ

       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), ioctl_eventpoll(2), poll(2), select(2)

ПЕРЕВОД

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

       Этот  перевод является свободной программной документацией; он распространяется на условиях общедоступной
       лицензии GNU (GNU General Public License - GPL, https://www.gnu.org/licenses/gpl-3.0.html  версии  3  или
       более поздней) в отношении авторского права, но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ.

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

Справочные страницы Linux 6.9.1                  12 июня 2024 г.                                        epoll(7)