Первый параметр, 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: }
13.1.3. Мультиплексирование с помощью select()
Системный вызов poll() был изначально представлен как часть Unix-дерева System V. Усилиями разработчиков BSD та же основная проблема была решена похожим способом — предоставлением системного вызова select().
#include <sys/select.h>
int select(int numfds, fd_set * readfds, fd_set * writefds,
fd_set * exceptfds, struct timeval * timeout);
Три промежуточных параметра — readfds, writefds и exceptfds — определяют, за какими файловыми дескрипторами необходимо следить. Каждый параметр — это указатель на fd_set, структуру данных, позволяющую процессу определить произвольное количество файловых дескрипторов[74]. Ею манипулируют с помощью перечисленных ниже макросов.
FD_ZERO(fd_set * fds); Очищает fds — в наборе не содержатся файловые дескрипторы. Этот макрос используется для инициализации структур fd_set. FD_SET(intfd, fd_set * fds); Добавляет fd к fd_set. FD_CLR(intfd, fd_set * fds); Удаляет fd из fd_set. FD_ISSET(int fd, fd_set * fds); Возвращает true, если fd содержится в установленном fds.
Первый набор файловых дескрипторов select(), readfds, содержит перечень файловых дескрипторов, вызывающих возврат вызова select(), когда они готовы для чтения[75] или (для каналов и сокетов) когда процесс на другом конце файла закрыл его. Когда любой файловый дескриптор в writefds готов к записи, select() возвращается, exceptfds содержит файловые дескрипторы для слежения за исключительными условиями. В Linux (так же, как и в Unix) это происходит только при поступлении внешних данных в сетевое подключение. В качестве любого из них можно указать NULL, если тот или иной тип события вас не интересует.
Окончательный параметр, timeout, определяет, насколько долго (в миллисекундах) вызову select() необходимо ожидать какого-либо события. Это указывает на struct timeval, которая выглядит следующим образом.
#include <sys/time.h>
struct timeval {
int tv_sec; /* секунды */
int tv_usec; /* микросекунды */
};
Первый элемент — tv_sec — это количество оставшихся секунд, a tv_usec — это количество оставшихся микросекунд. Если значением timeout является NULL, select() блокируется до следующего события. Если он указывает на struct timeval, содержащую 0 в обоих элементах, вызов select() не блокируется. Он обновляет наборы файловых дескрипторов, чтобы определить, какой файловый дескриптор в настоящее время готов для чтения или записи, а затем немедленно возвращается.
Первый параметр, numfds, вызывает наибольшие трудности. Он задает количество файловых дескрипторов (начиная с файлового дескриптора 0), которое может быть определено с помощью fd_sets. Еще один (и, возможно, более легкий) способ поведения numfds намного лучше максимального файлового дескриптора select()[76].
Поскольку Linux обычно позволяет каждому процессу иметь до 1024 файловых дескрипторов, numfds избавляет ядро от необходимости просмотра всех 1024 файловых дескрипторов, которые может содержать каждая структура fd_set, что улучшает показатели производительности.