37: /* читать из другого файлового дескриптора */
38: fd = (fd + 1) % 2;
39: }
40: }
Важное различие между mpx-nonblock
и mpx-blocks
состоит в том, что программа mpx-nonblock
не закрывается, когда один из каналов, из которого она считывает, закрыт. Неблокируемый read()
из канала без записывающих устройств возвращает 0 байт, из канала с таковыми, но без доступных данных read()
возвращает EAGAIN
.
Простое переключение неблокируемого ввода-вывода между дескрипторами файлов достается высокой ценой. Программа всегда опрашивает два файловых дескриптора для ввода — она никогда не блокируется. Постоянная работа программы приносит системе массу проблем, поскольку операционная система не может перевести процесс в режим ожидания (попробуйте запустить 10 копий mpx-nonblock
в своей системе и посмотрите, как это скажется на ее производительности).
13.1.2. Мультиплексирование с помощью poll()
Для эффективного мультиплексирования Linux предоставляет системный вызов poll()
, позволяющий процессу блокировать одновременно несколько файловых дескрипторов. Постоянно проверяя каждый файловый дескриптор, процесс создает отдельный системный вызов, определяющий, из каких файловых дескрипторов процесс будет читать, а на какие — записывать. Когда один или несколько таких файлов имеют данные, доступные для чтения, или могут принимать данные, записываемые в них, poll
() завершается, и приложение может считывать и записывать данные в дескрипторах, не беспокоясь о блокировке. После обработки этих файлов процесс создает еще один вызов poll()
, блокируемый до готовности файла. Ниже показано определение poll()
.
#include
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
.