Макросы вида FD_* оперируют структурами данных в пользовательском пространстве, а реализация вызова select() в ядре способна обслуживать наборы дескрипторов больших размеров. Тем не менее библиотека glibc не предусматривает простого способа изменения константы FD_SETSIZE. При необходимости изменить это ограничение придется отредактировать определение в соответствующих заголовочных файлах. glibc. Но если нужно отслеживать большое количество дескрипторов, то интерфейс epoll, вероятно, будет более предпочтительным по сравнению с вызовом select(). Причины этого будут описаны позже в данной главе.
Аргументы readfds, writefds и exceptfds возвращают результат выполнения. Структуры fd_set, на которые они указывают, должны содержать нужные нам наборы дескрипторов до вызова select(); здесь применяются макросы FD_ZERO() и FD_SET(). Вызов select() изменяет все эти структуры таким образом, что на момент возвращения они содержат наборы с готовыми дескрипторами. (Поскольку структуры изменяются во время вызова, их нужно заново инициализировать, если они используются многократно внутри цикла.) Содержимое структур можно изучить с помощью макроса FD_ISSET().
Если не интересует какой-то определенный вид событий, то соответствующему аргументу fd_set можно присвоить NULL. Подробности о каждом из трех видов событий будут описаны в подразделе 59.2.3.
Значение аргумента nfds должно быть на единицу больше, чем максимальный номер файлового дескриптора, содержащегося в любом из трех наборов. Этот аргумент делает вызов select() более эффективным, поскольку благодаря ему ядро знает, что файловые дескрипторы, чьи номера превышают данное значение, можно не проверять, ведь они точно не входят ни в один набор.
Аргумент timeout
Аргумент timeout влияет на поведение вызова select(), связанное с блокировкой. Ему можно присвоить либо NULL (в этом случае select() перманентно блокируется), либо указатель на структуру timeval:
struct timeval {
time_t tv_sec; /* Секунды */
suseconds_t tv_usec; /* Миллисекунды (long int) */
};
Если оба поля аргумент timeout равны 0, то вызов select() не блокируется; он просто проверяет заданные файловые дескрипторы на готовность и сразу же возвращается. В противном случае timeout определяет максимальное время ожидания вызова select().
Структура timeval позволяет указывать время с точностью до микросекунд, однако точность самого вызова ограничена системными часами (см. раздел 10.6). Согласно стандарту SUSv3 время ожидания округляется в большую сторону, если оно не делится без остатка.
Стандарт SUSv3 требует, чтобы максимально допустимое время ожидания не превышало 31 дня. Большинство реализаций UNIX допускают куда большие значения. На платформе Linux/x86-32 тип time_t представляет собой 32-разрядное целое число, поэтому максимальное значение измеряется многими годами.
Если аргумент timeout равен NULL или указывает на структуру, содержащую ненулевые поля, то вызов select() блокируется, пока не возникнет одно из следующих событий:
• хотя бы один из файловых дескрипторов, указанных в наборах readfds, writefds или exceptfds, становится готовым;
• вызов прерывается обработчиком сигнала;
• истекает время, указанное в аргументе timeout.
В старых версиях UNIX вызов sleep() поддерживал значения лишь с точностью до секунды (в отличие от, скажем, nanosleep()). Для эмуляции этой возможности аргументу nfds вызова select() передавался 0, аргументам readfds, writefds и exceptfds присваивался NULL, а в timeout указывался желаемый интервал.
Если в Linux вызов select() возвращается, сигнализируя о готовности одного или нескольких файловых дескрипторов, и при этом время ожидания не равно NULL, то структура, на которую указывает аргумент timeout, в итоге обновляется и сообщает о том, сколько времени оставалось до истечения заданного интервала. Но стоит отметить, что данное поведение зависит от реализации. Стандарт SUSv3 позволяет оставлять структуру, на которую указывает timeout, без изменений — именно
Стандарт SUSv3 гласит, что структура, на которую указывает аргумент timeout, может быть обновлена только в случае успешного завершения вызова select(). Однако в Linux обновление происходит, даже если вызов прерывается обработчиком сигнала (и, как следствие, завершается ошибкой EINTR). Это делается для того, чтобы сообщить о том, сколько осталось до истечения времени ожидания (если бы вызов завершился успешно).
Если предварительно воспользоваться системным вызовом personality() (доступным только в Linux), чтобы установить двоичный программный интерфейс (ABI), содержащий бит STICKY_TIMEOUTS, то вызов select() не станет изменять структуру, на которую указывает timeout.
Значение, возвращаемое вызовом select()