Книга: Разработка приложений в среде Linux. Второе издание
13.1.5. Мультиплексирование с помощью epoll
13.1.5. Мультиплексирование с помощью epoll
В версии 2.6 ядра Linux был предложен третий метод для мультиплексированного ввода-вывода по имени epoll
. Будучи более сложным, чем poll()
или select()
, epoll
ликвидирует узкие места, связанные с производительностью, которые характерны для обоих методов.
Оба системных вызова poll()
и select()
передают на проверку полный список файловых дескрипторов при каждом вызове. Каждый из этих дескрипторов должен быть обработан системным вызовом, даже если только один из них готов к чтению или записи. Когда проверяются десятки, сотни или тысячи файловых дескрипторов, эти системные вызовы превращаются в узкие места; ядро тратит много времени на выяснение того, какие именно файловые дескрипторы приложению необходимо проверить.
При использовании epoll
приложения обеспечивают ядро списком файловых дескрипторов для проверки с помощью одного системного вызова, а затем для проверки этих дескрипторов с помощью другого системного вызова. После создания списка ядро постоянно проверяет эти дескрипторы для событий, интересующих приложение[79], а затем сообщает о событии. Как только приложение запрашивает у ядра файловые дескрипторы, готовые для дальнейшей обработки, ядро предоставляет список без необходимости проверки каждого файлового дескриптора.
Преимущества в плане производительности epoll
требуют более сложного, чем у poll()
или select()
, интерфейса системных вызовов. В то время как poll()
использует массив struct pollfd
для предоставления набора файловых дескрипторов, a select()
с той же целью — три разных структуры fd_set
, epoll
перемещает эти наборы файловых дескрипторов в ядро, а не хранит их в адресном пространстве программы. На каждый из этих наборов ссылаются с помощью дескриптора epoll
, являющегося файловым дескриптором, который можно применять только для системных вызовов epoll
. Новые дескрипторы epoll распределяются системным вызовом epoll_create()
.
#include <sys/epoll.h>
int epoll_create (int numDescriptors);
Единственный параметр numDescriptors
— это наилучшее предположение программы о том, на какое количество файловых дескрипторов будет ссылаться заново созданный дескриптор epoll
. Это не жесткий предел, это просто подсказка ядру для более точной инициализации его внутренних структур. epoll_create()
возвращает дескриптор epoll
, а когда программа заканчивает работу с дескриптором, его следует передать close()
, чтобы позволить ядру освободить память, используемую этим дескриптором.
Хотя дескриптор epoll
является файловым дескриптором, его следует применять только с двумя системными вызовами.
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event * event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents,
int timeout);
Большинство этих параметров используют структуру struct epoll_event
, которая определяется, как показано ниже.
#include <sys/epoll.h>
struct epoll_event {
int events;
union {
void * ptr;
int fd;
unsigned int u32;
unsigned long long u64;
} data;
};
Эта структура обслуживает три цели: определяет, какие типы событий следует проверять, определяет типы произошедших событий и ассоциирует отдельный элемент данных с файловым дескриптором. Поле events
предназначено для первых двух функций и является одной или несколькими перечисленными далее значениями, объединенными с помощью логического "ИЛИ"[80].
EPOLLIN |
Определяет, что операция read() не блокируется; данные или уже готовы, или их уже не осталось для считывания. |
EPOLLOUT |
Связанный файл готов для записи. |
EPOLLPRI |
Файл имеет внешние данные, готовые для чтения. |
Второй элемент struct epoll_event, data
, представляет собой объединение, содержащее целое число (для хранения файлового дескриптора), указатель, а также 32- и 64-битные целые числа[81]. Этот элемент данных хранится в epoll
и возвращается в программу всякий раз, когда происходит событие подходящего типа. Элемент data
— это единственный способ, с помощью которого программе нужно выяснить, какой файловый дескриптор необходимо обслужить; интерфейс epoll
не передает файловый дескриптор программе, в отличие от poll()
и select()
(если data
не содержит файловый дескриптор). Этот метод обеспечивает дополнительную гибкость приложениям, которые отслеживают файлы как нечто, более сложное, чем простые файловые дескрипторы.
Системный вызов epoll_ctl()
добавляет файловые дескрипторы к набору, на который ссылается дескриптор epfdepoll
, и удаляет их из него.
Второй параметр, op
, описывает, каким образом следует модифицировать набор файловых дескрипторов, и является одним из перечисленных ниже.
EPOLL_CTL_ADD |
Файловый дескриптор fd добавляется к набору файловых дескрипторов набором событий events . Если файловый дескриптор уже присутствует, он возвращает EEXIST . (Несколько потоков могут добавлять тот же файловый дескриптор к набору epoll более одного раза, но это действие ничего не меняет.) |
EPOLL_CTL_DEL |
Файловый дескриптор fd удаляется из контролируемого набора файловых дескрипторов. Параметр events должен указывать на struct epoll_event , но содержимое этой структуры игнорируется. (Это еще раз доказывает, что events должен быть допустимым указателем; он не может быть NULL .) |
EPOLL_CTL_MOD |
Системный вызов struct epoll_event для fd обновляется на основе информации, на которую указывает events . Это позволяет контролировать набор событий и обновлять элемент данных, ассоциируемый с файловым дескриптором, не создавая условий состязания. |
Последним системным вызовом epoll
является epoll_wait()
, который блокирует до тех пор, пока один или несколько контролируемых файловых дескрипторов не будут иметь данные для чтения или же не будут готовы к записи. Первым аргументом является дескриптор epoll
, а последний — тайм-аутом в секундах. Если файловые дескрипторы не готовы к обработке до истечения тайм-аута, epoll_wait()
возвращает 0
.
Два промежуточных параметра определяют буфер для ядра, в который можно копировать структуры struct epoll_event
. Параметр events
указывает на буфер, maxevents
определяет, какое количество структур struct epoll_event
помещается в буфер, а возвращаемое значение сообщает программе количество структур, помещенных в этот буфер (пока вызов не попадет в состояние тайм-аута либо не произойдет ошибка).
Каждый системный вызов struct epoll_event
сообщает программе полное состояние контролируемого файлового дескриптора. Элемент events
может иметь установленные флаги EPOLLIN
, EPOLLOUT
или EPOLLPRI
, а также два новых флага, которые описаны ниже.
EPOLLERR |
С файлом связано ожидающее состояние ошибки; это случается, если ошибка происходит в сокете, когда приложение не считывает из него или не записывает в него. |
EPOLLHUP |
Файловый дескриптор завис; в главе 10 дана информация о том, когда это обычно происходит. |
На первый взгляд это все может показаться сложным, но на самом деле это очень похоже на работу poll()
. Вызов epoll_create()
— это то же, что и распределение массива struct pollfd
, a epoll_ctl()
— это то же, что и инициализация элементов этого массива. Главный цикл, обрабатывающий файловые дескрипторы, использует epoll_wait()
вместо системного вызова poll()
, а close()
аналогичен освобождению памяти, занимаемой массивом struct pollfd
. Эти параллели помогают переписывать с применением epoll
программы мультиплексирования, которые изначально были реализованы с помощью poll()
или select()
.
Интерфейс epoll
предлагает еще одну возможность, которую невозможно сравнить с poll()
или select()
. Поскольку дескриптор epoll
в действительности является файловым дескриптором (вот почему его можно передавать close()
), имеется возможность контролировать дескриптор epoll
как часть еще одного дескриптора epoll
либо через poll()
или select()
. Дескриптор epoll
будет готов к чтению из любого места, а вызов epoll_wait()
вернет события.
В окончательном решении проблемы мультиплексирования каналов, предложенном в данном разделе, используется epoll
. Оно очень похоже на другие примеры, вот только определенная часть кода инициализации перемещена в новую функцию addEvent()
для предотвращения нежелательного удлинения программы.
1: /* mpx-epoll.c */
2:
3: #include <fcntl.h>
4: #include <stdio.h>
5: #include <stdlib.h>
6: #include <sys/epoll.h>
7: #include <unistd.h>
8:
9: #include <sys/poll.h>
10:
11: void addEvent(int epfd, char * filename) {
12: int fd;
13: struct epoll_event event;
14:
15: if ((fd = open (filename, O_RDONLY | O_NONBLOCK)) < 0) {
16: perror("open");
17: exit(1);
18: }
19:
20: event.events = EPOLLIN;
21: event.data.fd = fd;
22:
23: if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event)) {
24: perror("epoll_ctl(ADD)");
25: exit(1);
26: }
27: }
28:
29: int main(void) {
30: char buf[4096];
31: int i, rc;
32: int epfd;
33: struct epoll_event events[2];
34: int num;
35: int numFds;
36:
37: epfd = epoll_create(2);
38: if (epfd < 0) {
39: perror("epoll_create");
40: return 1;
41: }
42:
43: /* открыть оба канала и добавить их в набор epoll */
44: addEvent(epfd, "p1");
45: addEvent(epfd, "p2");
46:
47: /* продолжать, пока есть один или более файловых дескрипторов
48: для слежения */
49: numFds = 2;
50: while (numFds) {
51: if ((num = epoll_wait(epfd, events,
52: sizeof(events) / sizeof(* events),
53: -1)) <= 0) {
54: perror("epoll_wait");
55: return 1;
56: }
57:
58: for (i = 0; i < num; i++) {
59: /* events[i].data.fd готов для чтения */
60:
61: rc = read(events[i].data.fd, buf, sizeof(buf) - 1);
62: if (rc < 0) {
63: perror("read");
64: return 1;
65: } else if (!rc) {
66: /* этот канал закрыт, не пытаться
67: читать из него снова */
68: if (epoll_ctl(epfd, EPOLL_CTL_DEL,
69: events[i].data.fd, &events[i])) {
70: perror("epoll_ctl (DEL)");
71: return 1;
72: }
73:
74: close(events[i].data.fd);
75:
76: numFds--;
77: } else {
78: buf[rc] = '';
79: printf("чтение: %s", buf);
80:
81: }
82: }
83:
84: close(epfd);
85:
86: return 0;
87: }
- 13.1.6 Сравнение poll() и epoll
- 13.1. Мультиплексирование входных и выходных данных
- 13.1.2. Мультиплексирование с помощью poll()
- 13.1.4. Сравнение poll() и select()
- 13.1.3. Мультиплексирование с помощью select()
- Повышение производительности приложений с помощью хранимых процедур
- Тестирование Web-сервиса XML с помощью WebDev.WebServer.exe
- Организация пользователей в группы с помощью ролей
- Обработка запросов с помощью PHP
- Как с помощью компьютера подшутить над друзьями и коллегами?
- Как составить психологический портрет с помощью Сети?
- Хочу следить за «здоровьем» винчестера. С помощью какой программы это можно делать?