Описываемый нами массив целых чисел, использующий по одному биту для каждого дескриптора, — это только один из возможных способов реализации функции select. Тем не менее является обычной практикой ссылаться на отдельные дескрипторы в наборе дескрипторов как на биты, например так: «установить бит для прослушиваемого дескриптора в наборе для чтения».
В разделе 6.10 мы увидим, что функция poll использует совершенно другое представление: массив структур переменной длины, по одной структуре для каждого дескриптора.
Например, чтобы определить переменную типа
fd_set
и затем установить биты для дескрипторов 1, 4 и 5, мы пишем:
fd_set rset;
FD_ZERO(&rset); /* инициализируем набор все биты сброшены */
FD_SET(1, &rset); /* устанавливаем бит для fd 1 */
FD_SET(4, &rset); /* устанавливаем бит для fd 4 */
FD_SET(5, &rset); /* устанавливаем бит для fd 5 */
Важно инициализировать набор, так как если набор будет создан в виде автоматической переменной и не проинициализировав, результат может оказаться непредсказуемым.
Любой из трех средних аргументов функции
select
—
readset
,
writeset
или
exceptset
— может быть задан как пустой указатель, если нас не интересует определяемое им условие. На самом деле, если все три указателя пустые, мы просто получаем таймер большей точности, чем обычная функция Unix
sleep
(позволяющая задавать время с точностью до секунды). Функция
poll
обеспечивает аналогичную функциональность. На рис. С.9 и С.10 [110] показана функция
sleep_us
, реализованная с помощью функций
select
и
poll
, которая позволяет устанавливать время ожидания с точностью до микросекунд.
Аргумент
maxfdp1
задает число проверяемых дескрипторов. Его значение на единицу больше максимального номера проверяемого дескриптора (поэтому мы назвали его
maxfdp1
). Проверяются дескрипторы 0, 1, 2 и далее до
maxfdp1
- 1 включительно.
Константа
FD_SETSIZE
, определяемая при подключении заголовочного файла
, является максимальным числом дескрипторов для типа данных
fd_set
. Ее значение часто равно 1024, но такое количество дескрипторов используется очень немногими программами. Аргумент
maxfdp1
заставляет нас вычислять наибольший интересующий нас дескриптор и затем сообщать ядру его значение. Например, в предыдущем коде, который включает дескрипторы 1, 4 и 5, значение аргумента
maxfdp1
равно 6. Причина, по которой это 6, а не 5, в том, что мы задаем количество дескрипторов, а не наибольшее значение, а нумерация дескрипторов начинается с нуля.
Зачем нужно было включать этот аргумент и вычислять его значение? Причина в том, что он повышает эффективность работы ядра. Хотя каждый набор типа fd_set может содержать множество дескрипторов (обычно до 1024), реальное количество дескрипторов, используемое типичным процессом, значительно меньше. Эффективность возрастает за счет того, что не копируются ненужные части набора дескрипторов между ядром и процессом и не требуется проверять биты, которые всегда являются нулевыми (см. раздел 16.13 [128]).
Функция
select
изменяет наборы дескрипторов, на которые указывают аргументы
readset
,
writeset
и
exceptset
. Эти три аргумента являются аргументами типа «значение-результат». Когда мы вызываем функцию, мы указываем интересующие нас дескрипторы, а по ее завершении результат показывает нам, какие дескрипторы готовы. Проверить определенный дескриптор из структуры
fd_set
после завершения вызова можно с помощью макроса
FD_ISSET
. Для дескриптора, не готового для чтения или записи, соответствующий бит в наборе дескрипторов будет сброшен. Поэтому мы устанавливаем все интересующие нас биты во всех наборах дескрипторов каждый раз, когда вызываем функцию
select
.