SELECT(2) Руководство программиста Linux SELECT(2)

ИМЯ

select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - многопоточный синхронный ввод-вывод

ОБЗОР

/* в соответствии с POSIX.1-2001, POSIX.1-2008 */
#include <sys/select.h>
/* в соответствии с ранними стандартами */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
#include <sys/select.h>
int pselect(int nfds, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, const struct timespec *timeout,
            const sigset_t *sigmask);

Требования макроса тестирования свойств для glibc (см. feature_test_macros(7)):

pselect(): _POSIX_C_SOURCE >= 200112L

ОПИСАНИЕ

Вызовы select() и pselect() позволяют программам отслеживать изменения нескольких файловых дескрипторов ожидая, когда один или более файловых дескрипторов станут «готовы» для операции ввода-вывода определённого типа (например, ввода). Файловый дескриптор считается готовым, если к нему возможно применить соответствующую операцию ввода-вывода (например, read(2) или очень маленький write(2)) без блокировки.

Вызов select() может следить только за номерами файловых дескрипторов, которые меньше FD_SETSIZE; вызов poll(2) не имеет этого ограничения. Смотрите ДЕФЕКТЫ.

Работа select() и pselect() идентична за исключением трёх моментов:

(i)
В вызове select() время ожидания задаётся в структуре struct timeval (с секундами и микросекундами), а в pselect() используется структура struct timespec (с секундами и наносекундами).
(ii)
Вызов select() может обновить аргумент timeout, который показывает сколько времени прошло. Вызов pselect() не изменяет этот аргумент.
(iii)
Вызов select() не имеет аргумента sigmask, и ведёт себя также как pselect(), если при вызове было указано значение sigmask равное NULL.

Отслеживаются 3 независимых набора файловых дескрипторов. В тех, что перечислены в readfds, будет отслеживаться появление символов, доступных для чтения (проверяется доступность чтения без блокировки; в частности, файловый дескриптор готов для чтения, если он указывает на конец файла). Файловые дескрипторы, указанные в writefds, будут отслеживаться для возможности записи без блокировки, если доступно пространство для записи (хотя при большом количестве данных для записи будет по-прежнему выполнена блокировка). Файловые дескрипторы, указанные в exceptfds, будут отслеживаться для обнаружения исключительных условий (примеры некоторых исключительных условий смотрите в описании POLLPRI из poll(2)).

При возврате из вызова наборы файловых дескрипторов изменяются, показывая какие файловые дескрипторы фактически изменили состояние (то есть, если используется select() в цикле, то наборы должны переинициализироваться перед каждым вызовом).

Значение каждого из трёх наборов файловых дескрипторов может быть задано как NULL, если слежение за определённым классом событий над файловыми дескрипторами не требуется.

Для манипуляций наборами существуют четыре макроса: FD_ZERO() очищает набор; FD_SET() добавляет заданный файловый дескриптор к набору; FD_CLR() удаляет файловый дескриптор из набора; FD_ISSET() проверяет, является ли файловый дескриптор частью набора. Эти макросы полезны после возврата из вызова select().

Значение nfds должно быть на единицу больше самого большого номера файлового дескриптора из всех трёх наборов плюс 1. Указанные файловые дескрипторы в каждом наборе проверяются до этого порога (но смотрите ДЕФЕКТЫ).

В аргументе timeout указывается интервал, на который должен заблокироваться select() в ожидании готовности файлового дескриптора. Вызов будет блокирован пока:

  • файловый дескриптор не станет готов;
  • вызов не прервётся обработчиком сигнала;
  • не истечёт время ожидания.

Заметим, что интервал timeout будет округлён с точностью системных часов, а из-за задержки при планировании в ядре блокирующий интервал будет немного больше. Если оба поля структуры timeval равны нулю, то select() завершится немедленно (полезно при опросе (polling)). Если значение timeout равно NULL (время ожидания не задано), то select() может блокировать работу неопределённо долго.

Значение sigmask является указателем на маску сигналов (смотрите sigprocmask(2)); если оно не равно NULL, то сначала pselect() заменяет текущую маску сигналов на заданную sigmask, затем выполняет функцию «select», после чего восстанавливает первоначальную сигнальную маску.

Кроме различия в точности аргумента timeout вызов pselect()

ready = pselect(nfds, &readfds, &writefds, &exceptfds,

                timeout, &sigmask);

эквивалентен атомарному выполнению следующих вызовов:

sigset_t origmask;
pthread_sigmask(SIG_SETMASK, &sigmask, &origmask);
ready = select(nfds, &readfds, &writefds, &exceptfds, timeout);
pthread_sigmask(SIG_SETMASK, &origmask, NULL);

Причина необходимости pselect() в том, что если нужно ждать какого-то сигнала или готовности файлового дескриптора, то необходимо атомарное тестирование для избежания состязательности. (Предположим, что обработчик сигнала устанавливает глобальный флаг и завершается. В этом случае тест этого глобального флага после вызова select() мог бы длиться бесконечно, если сигнал прибыл бы сразу после тестирования, но до вызова. В отличие от этого, pselect() позволяет сначала заблокировать сигналы, обработать уже поступившие и затем вызвать pselect() с желаемым значением sigmask, избегая состязательности.)

Время ожидания

Используемые структуры времени определены в <sys/time.h> и выглядят следующим образом:

struct timeval {

    long    tv_sec;         /* секунды */

    long    tv_usec;        /* микросекунды */
};

и

struct timespec {

    long    tv_sec;         /* секунды */

    long    tv_nsec;        /* наносекунды */
};

(Однако, смотрите ниже про версию POSIX.1.)

Иногда select() вызывается с пустыми наборами (всеми тремя), nfds равным нулю и непустым timeout для переносимой реализации перехода в режим ожидания (sleep) на периоды с точностью менее секунды.

В Linux вызов select() изменяет timeout для отражения времени, проведённого не в режиме ожидания; большая часть других реализаций этого не делает (согласно POSIX.1 допускается любой из этих вариантов). Это вызывает проблемы как при переносе кода Linux, читающего timeout, на другие операционные системы, так и при переносе на Linux кода, использующего struct timeval для многократного вызова select() в цикле без его переинициализации. Во избежание этого следует считать, что значение timeout не определено после возврата из select().

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ

При успешном выполнении select() и pselect() возвращают количество файловых дескрипторов, находящихся в трёх возвращаемых наборах (то есть, общее количество бит, установленных в readfds, writefds, exceptfds), при чём это количество может быть нулевым, если время ожидания истекло, а интересующие события так и не произошли. При ошибке возвращается значение -1, а переменной errno присваивается соответствующий номер ошибки; наборы файловых дескрипторов не изменяются и значение timeout становится неопределённым.

ОШИБКИ

В одном из наборов находится неверный файловый дескриптор (возможно файловый дескриптор уже закрыт, или при работе с ним произошла ошибка). Однако смотрите ДЕФЕКТЫ.
При выполнении поступил сигнал; см. signal(7).
Значение nfds отрицательно или превышает ограничение ресурса RLIMIT_NOFILE (смотрите getrlimit(2)).
Значение, содержащееся внутри timeout, некорректно.
Не удалось выделить память для внутренних таблиц.

ВЕРСИИ

Вызов pselect() был добавлен в ядро Linux версии 2.6.16. До этого pselect() эмулировался в glibc (но, см. ДЕФЕКТЫ).

СООТВЕТСТВИЕ СТАНДАРТАМ

Вызов select() соответствует POSIX.1-2001, POSIX.1-2008 и 4.4BSD (впервые select() появился в 4.2BSD). Обычно перенос выполняется с не-BSD систем и на них, если они поддерживают уровень BSD-сокетов (включая варианты System V). Однако, заметим, что вариант System V, обычно, устанавливает значение переменной timeout перед выходом, а вариант BSD - нет.

Вызов pselect() определён в стандартах POSIX.1g, в POSIX 1004.1-2001 и POSIX.1-2008.

ЗАМЕЧАНИЯ

fd_set представляет собой буфер фиксированного размера. Выполнение FD_CLR() или FD_SET() с отрицательным значением fd, равным или большим чем FD_SETSIZE, приводит к неопределённому поведению. Более того, согласно POSIX fd должен быть корректным файловым дескриптором.

На операции select() и pselect() флаг O_NONBLOCK не влияет.

В некоторых системах UNIX вызов select() может завершаться с ошибкой EAGAIN, если системе не удаётся выделить внутренние ресурсы ядра, вместо ошибки ENOMEM, как это происходит в Linux. В POSIX эта ошибка указана для poll(2), но не для select(). В переносимых программах лучше ожидать EAGAIN в цикле, как для EINTR.

В системах без pselect(), надёжного (и более переносимого) перехвата сигнала можно достичь с помощью трюка с каналом в самого себя. В этом методе обработчик сигнала пишет байт в канал, чей второй конец отслеживается select() в основной программе (чтобы избежать возможной блокировки при записи в канал, который может быть заполнен, или при чтении из канала, который может быть пуст, нужно использовать неблокирующий ввод/вывод).

Что касается задействованных типов, классическим вариантом является структура timeval с двумя полями типа long (как показано ниже), которая определена в <sys/time.h>. В POSIX.1:

struct timeval {

    time_t         tv_sec;     /* секунды */

    suseconds_t    tv_usec;    /* микросекунды */
};

где структура определена в <sys/select.h>, а типы данных time_t и suseconds_t определены в <sys/types.h>.

Что касается прототипов, классическим вариантом является объявление select() в <time.h>. Согласно POSIX.1 объявления select() и pselect() должны включаться в <sys/select.h>.

В <sys/select.h> из glibc 2.0 указан ошибочный прототип pselect(). В glibc 2.1 до версии 2.2.1 pselect() доступен при определённом _GNU_SOURCE. Требования, которые необходимы для работы с glibc начиная с версии 2.2.2, показаны в разделе ОБЗОР.

Соотношение между уведомлениями select() и poll()

Внутри исходного кода ядра Linux имеются определения, которые показывают соотношение между уведомлениями чтения, записи и исключительного условия select() и уведомляющими событиями, предоставляемыми poll(2)epoll(7)):

#define POLLIN_SET (POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP |

                    POLLERR)

                   /* готов для чтения */
#define POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR)

                   /* готов для записи */
#define POLLEX_SET (POLLPRI)

                   /* исключительное условие */

Многонитевые приложения

Если файловой дескриптор, отслеживаемый select(), закрывается в другой нити, то результат непредсказуем. В некоторых системах UNIX select() разблокируется и возвращает выполнение с указанием того, что файловый дескриптор готов (последующая операция ввода-вывода, вероятно, завершится с ошибкой, если другой процесс не переоткроет файловый дескриптор между возвратом select() и выполнением операции ввода-вывода). В Linux (и некоторых других системах), закрытие файлового дескриптора в другой нити не влияет на select(). Резюмируя, любое приложение, которое полагается на определённое поведение в таком сценарии, должно считаться ошибочным.

Отличия между библиотекой C и ядром

Ядро Linux разрешает наборы файловых дескрипторов любого размера, определяя длину набора по значению nfds. Однако в реализации glibc тип fd_set имеет фиксированный размер. Смотрите также ДЕФЕКТЫ.

Интерфейс pselect(), описанный в этой странице, реализован в glibc. Для этого используется системный вызов pselect6(). Поведение данного системного вызова несколько отличается от обёрточной функции glibc.

В Linux системный вызов pselect6() изменяет содержимое своего аргумента timeout. Однако, обёрточная функция glibc скрывает это поведение используя локальную переменную для аргумента timeout при передаче в системный вызов. Таким образом, функция pselect() в glibc не изменяет свой аргумент timeout; это поведение требуется в POSIX.1-2001.

Последний аргумент системного вызова pselect6() не является указателем sigset_t *, он представляет собой структуру в виде:

struct {

    const kernel_sigset_t *ss;   /* указатель на набор сигналов */

    size_t ss_len;               /* размер (в байтах) объекта,

                                    на который указывает «ss» */
};

Это позволяет системному вызову получить указатель на набор сигналов и его размер, так как в большинстве архитектур системным вызовам можно передать максимум 6 аргументов. В sigprocmask(2) смотрите описание различий между обозначением набора сигналов в ядре и libc.

ДЕФЕКТЫ

В POSIX разрешено в реализации определять верхнее ограничение, объявляемое через константу FD_SETSIZE, для диапазона файловых дескрипторов, который можно задать в наборе файловых дескрипторов. Ядро Linux не имеет фиксированного ограничения, но реализация glibc использует тип fd_set постоянного размера, определяет FD_SETSIZE равным 1024, а также предоставляет набор макросов операций FD_*(), учитывающих это ограничение. Для слежения за файловыми дескрипторами большими чем 1023 используйте вызов poll(2).

Реализация аргументов fd_set в виде аргументов значение-результат означает, что они должен инициализироваться перед каждым вызовом select(). Такой ошибки проектирования избежали в poll(2), где используется отдельные поля структуры для входных и выходных параметров вызова.

Согласно POSIX, select() должен проверять все файловые дескрипторы, указанные в трёх наборах файловых дескрипторов до верхнего порога nfds-1. Однако текущая реализация игнорирует любой файловый дескриптор в этих наборах, значение которого больше максимального номера файлового дескриптора, который в настоящий момент открыт процессом. Согласно POSIX, такой файловый дескриптор, указанный в любом наборе, должен приводить к ошибке EBADF.

Glibc 2.0 предоставляет версию pselect(), которая не принимает аргумент sigmask.

Начиная с версии 2.1, glibc предоставляет эмуляцию pselect(), которая реализована с помощью sigprocmask(2) и select(). Эта реализация остаётся уязвимой к той самой состязательности, для устранения которой и был разработан pselect(). В современных версии glibc используется (бессостязательный) системный вызов pselect(), если он предоставляется ядром.

В Linux, вызов select() может сообщать о файловом дескрипторе сокета как о «готовом для чтения», хотя при последующем чтении произойдёт блокировка. Это может случиться, например, когда данные прибыли, но при анализе их контрольная сумма не совпала и они были отброшены. Также могут быть другие обстоятельства, при которых файловый дескриптор ошибочно считается готовым. Поэтому, возможно безопасней будет использовать для сокетов O_NONBLOCK, которые не должны блокироваться.

В Linux, вызов select() также изменяет timeout, если он прерван обработчиком сигнала (т. е., возвращается ошибка EINTR). Согласно POSIX.1 это не разрешено. В Linux системный вызов pselect() действует также, но обёртка glibc скрывает это поведение копируя перед вызовом timeout в локальную переменную и передавая её в системный вызов.

ПРИМЕР

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int
main(void)
{

    fd_set rfds;

    struct timeval tv;

    int retval;

    /* Следить, когда в stdin (fd 0) что-нибудь появится. */

    FD_ZERO(&rfds);

    FD_SET(0, &rfds);

    /* Ждать не больше пяти секунд. */

    tv.tv_sec = 5;

    tv.tv_usec = 0;

    retval = select(1, &rfds, NULL, NULL, &tv);

    /* Больше не полагаться на значение tv! */

    if (retval == -1)

        perror("select()");

    else if (retval)

        printf("Есть данные.\n");

        /* FD_ISSET(0, &rfds) will be true. */

    else

        printf("Данные не появились в течение пяти секунд.\n");

    exit(EXIT_SUCCESS);
}

СМОТРИТЕ ТАКЖЕ

accept(2), connect(2), poll(2), read(2), recv(2), restart_syscall(2), send(2), sigprocmask(2), write(2), epoll(7), time(7)

Обсуждение и примеры смотрите в select_tut(2).

2019-03-06 Linux