В табл. 59.5 собраны подробности о записывающем конце канала. Мы исходим из того, что поле events вызова poll() содержит флаг POLLOUT. Столбец «Место для PIPE_BUF байт?» указывает на то, достаточно ли в канале места для автоматической записи PIPE_BUF байтов без блокировки. Это критерий, по которому Linux определяет готовность канала к записи. Ряд других реализаций UNIX поступает аналогичным образом. Но есть системы, считающие канал доступным для записи, если в него можно записать хотя бы один байт. (В Linux 2.6.10 и ниже вместимость именованного канала была равна PIPE_BUF; это значит, что канал считался недоступным для записи при наличии в нем хотя бы одного байта данных.)
Таблица 59.5. Параметры, возвращаемые вызовами select() и poll() для записывающего конца именованного канала и очереди FIFO
Условие или событие
select()
poll()
Данные в канале?
Записывающий конец открыт?
Нет
Нет
w
POLLERR
Да
Да
w
POLLOUT
Да
Нет
w
POLLOUT | POLLERR
В некоторых других UNIX-системах, если считывающий конец канала закрыт, то вызов poll() возвращается с установленным битом POLLERR, а не POLLHUP. Проверяя возможность блокировки операции записи, портируемые приложения должны учитывать оба эти бита.
Сокеты
В табл. 59.6 описывается поведение вызовов select() и poll() для сокетов. В столбце poll() предполагается, что поле events равно (POLLIN | POLLOUT | POLLPRI). В столбце select() мы исходим из такого условия: файловый дескриптор проверяется на возможность ввода, вывода или наличия исключительного условия (то есть дескриптор указан во всех трех наборах, которые передаются в select()). Эта таблица охватывает только распространенные сценарии.
В ОС Linux поведение вызова poll() для сокетов домена UNIX отличается от показанного в табл. 59.6, если проверка выполняется после закрытия удаленного конца соединения: помимо прочих флагов в поле revents возвращается бит POLLHUP.
Таблица 59.6. Параметры, возвращаемые вызовами select() и poll() для сокетов
Условие или событие — select() — poll()
Доступен ввод — r — POLLIN
Возможен вывод — w — POLLOUT
Слушающий сокет установил входящее соединение — r — POLLIN
Получены внеканальные данные (только для TCP) — x — POLLPRI
Удаленный потоковый сокет разорвал соединение или выполнил shutdown(SHUT_WR) — rw — POLLIN | POLLOUT | POLLRDHUP
Флаг POLLRDHUP (доступный только в Linux 2.6.17 и выше) требует дополнительного разъяснения. На самом деле он имеет вид EPOLLRDHUP и предназначен в основном для использования в режиме срабатывания по фронту программного интерфейса epoll (см. раздел 59.4). Он возвращается, когда потоковый сокет на другом конце соединения закрывает свой записывающий канал. Этот флаг позволяет приложению задействовать интерфейс epoll, срабатывающий по фронту, чтобы упростить распознавание удаленного закрытия (альтернативой было бы определить наличие флага POLLIN и выполнить операцию read(), которая в случае удаленного закрытия возвращает 0).
59.2.4. Сравнение вызовов select() и poll()
В этом подразделе мы сосредоточим внимание на сходствах и отличиях, присущих вызовам select() и poll().
Отличия в программных интерфейсах
Ниже перечислены отдельные различия между программными интерфейсами select() и poll().
• Использование типа данных fd_set ограничивает максимальное количество файловых дескрипторов, которое можно отследить с помощью вызова select() (FD_SETSIZE). В Linux это ограничение по умолчанию равно 1024, а его изменение требует повторной компиляции приложения. Для сравнения: вызов poll() не имеет никаких внутренних ограничений на диапазон отслеживаемых файловых дескрипторов.
• Аргументы типа fd_set возвращают результат, поэтому, если вызов select() выполняется в цикле, их нужно заново инициализировать на каждой итерации. Вызов poll() применяет отдельные поля events (для ввода) и revents (для вывода), так что на него данное требование не распространяется.
• Вызов select() обеспечивает более высокую точность времени ожидания по сравнению с poll() (микросекунды вместо миллисекунд). Хотя в обоих случаях точность ограничивается системными часами.
• Если один из наблюдаемых файловых дескрипторов был закрыт, то вызов poll() установит бит POLLNVAL в поле revents соответствующего дескриптора. Для сравнения: вызов select() просто возвращает –1 с ошибкой EBADF; чтобы определить, какой именно дескриптор закрылся, придется проверять ошибки соответствующих системных вызовов ввода/вывода. Однако в большинстве случаев эта разница несущественна, так как приложение обычно способно само отслеживать закрывающиеся дескрипторы.
Портируемость