Книга: Разработка приложений в среде Linux. Второе издание

13.1.2. Мультиплексирование с помощью poll()

13.1.2. Мультиплексирование с помощью poll()

Для эффективного мультиплексирования Linux предоставляет системный вызов poll(), позволяющий процессу блокировать одновременно несколько файловых дескрипторов. Постоянно проверяя каждый файловый дескриптор, процесс создает отдельный системный вызов, определяющий, из каких файловых дескрипторов процесс будет читать, а на какие — записывать. Когда один или несколько таких файлов имеют данные, доступные для чтения, или могут принимать данные, записываемые в них, poll() завершается, и приложение может считывать и записывать данные в дескрипторах, не беспокоясь о блокировке. После обработки этих файлов процесс создает еще один вызов poll(), блокируемый до готовности файла. Ниже показано определение poll().

#include <sys/poll.h>
int poll(struct pollfd * fds, int numfds, int timeout);

Последние два параметра очень просты; numfds задает количество элементов в массиве, на который указывает первый параметр, a timeout определяет, насколько долго poll() должна ожидать события. Если в качестве тайм-аута задается 0, poll() никогда не входит в состояние тайм-аута.

Первый параметр, fds, описывает, какие файловые дескрипторы следует контролировать, и для каких типов ввода-вывода. Это указатель на массив структур struct pollfd.

struct pollfd {
 int fd;        /* файловый дескриптор */
 short events;  /* ожидаемые события ввода-вывода */
 short revents; /* происшедшие события ввода-вывода */
};

Первый элемент, fd, является контролируемым файловым дескриптором, а элемент events описывает, какие типы событий подлежат мониторингу. Последний представляет собой один или несколько перечисленных флагов, объединенных с помощью логического "ИЛИ".

POLLIN Нормальные данные доступны для считывания из файлового дескриптора.
POLLPRI Приоритетные (внешние) данные доступны для считывания.
POLLOUT Файловый дескриптор может принимать записываемые на него данные.

Элемент revents структуры struct pollfd заполняется системным вызовом poll() и отражает состояние файлового дескриптора fd. Это похоже на элемент events, но вместо определения интересующих приложение событий ввода-вывода он определяет доступные такие типы. Например, если приложение контролирует канал как для чтения, так и для записи (events установлено в POLLIN | POLLOUT), после успешного вызова poll() в revents устанавливается бит POLLIN, если канал готов для чтения, и бит POLLOUT, если в канале имеется пространство для записи дополнительных данных. Если верно и то, и другое, устанавливаются оба бита.

Существует несколько битов, которые ядро может установить в revents, но которые невозможно установить в events.

POLLERR В дескрипторе файла имеется ожидающая ошибка; выполнение системного вызова на файловом дескрипторе приведет к установке errno в подходящий код.
POLLHUP Файл был отключен; в него больше невозможно ничего записывать (хотя могут остаться данные для считывания). Это происходит в случае отключения терминала либо закрытия удаленного конца канала или сокета.
POLLNVAL Файловый дескриптор недоступен (он не относится к открытому файлу).

Возвращаемое значение poll() равно нулю в случае тайм-аута вызова, -1 в случае ошибки (например, fds — неверный указатель; ошибки в самих файлах вызывают установку POLLERR), или же положительное число, описывающее количество файлов с ненулевыми элементами revents.

В отличие от неэффективного метода мультиплексирования входных и выходных данных из каналов, используемого ранее, poll() довольно легко решает ту же проблему. Применяя poll() к файловым дескрипторам одновременно для обоих каналов, мы знаем, что когда poll() возвращается, один из каналов готов для чтения либо закрыт. Мы проверяем элемент revents для обоих файловых дескрипторов, чтобы узнать, какие действия предпринять, и по завершении возвращаемся в вызов poll(). Теперь большая часть времени тратится на блокирование вызова poll(), а не на постоянную проверку файловых дескрипторов, использующих неблокируемый ввод-вывод, что значительно уменьшает нагрузку на систему. Ниже показан код mpx-poll.

 1: /* mpx-poll.с */
 2:
 3: #include <fcntl.h>
 4: #include <stdio.h>
 5: #include <sys/poll.h>
 6: #include <unistd.h>
 7:
 8: int main(void) {
 9:  struct pollfdfds[2];
10:  char buf [4096];
11:  int i, rc;
12:
13:  /* открыть оба канала */
14:  if ( (fds[0].fd = open("p1", O_RDONLY | O_NONBLOCK)) < 0) {
15:   perror("open p1");
16:   return 1;
17:  }
18:
19:  if ((fds[1].fd = open("p2", O_RDONLY | O_NONBLOCK)) < 0) {
20:   perror("open p2");
21:   return 1;
22:  }
23:
24:  /* начать чтение из обоих файловых дескрипторов */
25:  fds[0].events = POLLIN;
26:  fds[1].events = POLLIN;
27:
28:  /* пока наблюдаем за одним из fds[0] или fds[1] */
29:  while (fds[0].events || fds[1].events ) {
30:   if (poll(fds, 2, 0) < 0) {
31:    perror("poll");
32:    return 1;
33:   }
34:
35:   /* проверить, какой из файловых дескрипторов
36:      готов для чтения из него */
37:   for (i = 0; i < 2; i++) {
38:    if (fds[i].revents) {
39:     /* fds[i] готов для чтения, двигаться дальше... */
40:     rc = read(fds[i].fd, buf, sizeof(buf) - 1);
41:     if (rc < 0) {
42:      perror("read");
43:      return 1;
44:     } else if (!rc) {
45:      /* этот канал закрыт, не пытаться
46:         читать из него снова */
47:      fds[i].events = 0;
48:     } else {
49:      buf[rc] = '';
50:      printf("чтение : %s", buf);
51:     }
52:    }
53:   }
54:  }
55:
56:  return 0;
57: }

Оглавление книги


Генерация: 1.568. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз