Интерфейс 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
Методы poll() и epoll существенно отличаются; poll() хорошо стандартизован, но плохо масштабируется, в то время как epoll существует только в Linux, но очень хорошо масштабируется. Приложения, наблюдающие за небольшим количеством файловых дескрипторов и переносимости величин, должны использовать poll(), но любому приложению, которому необходимо контролировать большое количество дескрипторов, лучше применять epoll, даже если ему нужно поддерживать poll() для других платформ.
Отличия в производительности двух методов поразительны. Чтобы продемонстрировать, насколько лучше масштабируется epoll, в коде poll-vs-epoll.с измеряется количество системных вызовов poll() и epoll_wait(), которые можно создать за одну секунду для наборов файловых дескрипторов разных размеров (количество файловых дескрипторов для помещения в набор задается в командной строке). Каждый файловый дескриптор ссылается на считывающую часть канала, и они создаются с помощью dup2().
В табл. 13.1 суммируются результаты запуска poll-vs-epoll.с для установленных размеров диапазоном от одного до 100 000 файловых дескрипторов[82]. В то время как количество системных вызовов в секунду резко падает для poll(), оно остается почти постоянным для epoll[83]. Как поясняет эта таблица, epoll добавляет в систему намного меньше нагрузки, чем poll(), и в результате гораздо лучше масштабируется.
Таблица 13.1. Результаты сравнения poll() и epoll()
Файловые дескрипторы poll() epoll() 1 310063 714848 10 140842 726108 100 25866 726659 1000 3343 729072 5000 612 718424 10000 300 730483 25000 108 717097 50000 38 729746 100000 18 712301
1: /* poll-vs-epoll.с */
2:
3: #include <errno.h>
4: #include <fcntl.h>
5: #include <stdio.h>
6: #include <sys/epoll.h>
7: #include <sys/poll.h>
8: #include <sys/signal.h>
9: #include <unistd.h>
10: #include <sys/resource.h>
11: #include <string.h>
12: #include <stdlib.h>
13:
14: #include <sys/select.h>
15:
16: int gotAlarm;
17:
18: void catch(int sig) {
19: gotAlarm = 1;
20: }
21:
22: #define OFFSET 10
23:
24: int main(int argc, const char ** argv) {
25: int pipeFds[2];
26: int count;
27: int numFds;
28: struct pollfd * pollFds;
29: struct epoll_event event;
30: int epfd;
31: int i;
32: struct rlimit lim;
33: char * end;
34:
35: if (!argv[1]) {
36: fprintf(stderr, "ожидалось числоn");
37: return 1;
38: }
39:
40: numFds = strtol(argv[1], &end, 0);
41: if (*end) {
42: fprintf(stderr, "ожидалось числоn");
43: return 1;
44: }
45:
46: printf("Запуск теста для %d файловых дескрипторов.n", numFds);
47:
48: lim.rlim_cur = numFds + OFFSET;
49: lim.rlim_max = numFds + OFFSET;
50: if (setrlimit(RLIMIT_NOFILE, &lim)) {
51: perror("setrlimit");
52: exit(1);
53: }
54:
55: pipe(pipeFds);
56:
57: pollFds = malloc(sizeof (*pollFds) * numFds);
58:
59: epfd = epoll_create(numFds);
60: event.events = EPOLLIN;
61:
62: for (i = OFFSET; i < OFFSET + numFds; i++) {
63: if (dup2(pipeFds[0], i) != i) {
64: printf("сбой в %d: %sn", i, strerror(errno));
65: exit(1);
66: }
67:
68: pollFds[i - OFFSET].fd = i;
69: pollFds[i - OFFSET].events = POLLIN;
70:
71: event.data.fd = i;
72: epoll_ctl(epfd, EPOLL_CTL_ADD, i, &event);
73: }
74:
75: /* с помощью signal выяснить, когда время истекло */
76: signal(SIGALRM, catch);
77:
78: count = 0;
79: gotAlarm = 0;
80: alarm(1);
81: while (!gotAlarm) {
82: poll(pollFds, numFds, 0);
83: count++;
84: }
85:
86: printf("Вызовов poll() в секунду: %dn", count);
87:
88: alarm(1);