Три средних аргумента,
readset
,
writeset
и
exceptset
, определяют дескрипторы, которые ядро должно проверить на возможность чтения и записи и на наличие исключений (exceptions). В настоящее время поддерживается только два исключения:
1. На сокет приходят внеполосные данные. Более подробно мы опишем этот случай в главе 24.
2. Присутствие информации об управлении состоянием (control status information), которая должна быть считана с управляющего (master side) псевдотерминала, помещенного в режим пакетной обработки. Псевдотерминалы в данном томе не рассматриваются.
Проблема в том, как задать одно или несколько значений дескрипторов для каждого из трех аргументов. Функция
select
использует
наборы дескрипторов, обычно это массив целых чисел, где каждый бит в каждом целом числе соответствует дескриптору. Например, при использовании 32-разрядных целых чисел первый элемент массива (целое число) соответствует дескрипторам от 0 до 31, второй элемент — дескрипторам от 32 до 63, и т.д. Детали реализации не влияют на приложение и скрыты в типе данных
fd_set
и следующих четырех макросах:
void FD_ZERO(fd_set *<i>fdset</i>); /* сбрасываем все биты в <i>fdset</i> */
void FD_SET(int <i>fd</i>, fd_set *<i>fdset</i>); /* устанавливаем бит для <i>fd</i> в <i>fdset</i> */
void FD_CLR(int <i>fd</i>, fd_set *<i>fdset</i>); /* сбрасываем бит для <i>fd</i> в <i>fdset</i> */
int FD_ISSET(int <i>fd</i>, fd_set *<i>fdset</i>); /* установлен ли бит для <i>fd</i> в <i>fdset</i>? */
Мы размещаем в памяти набор дескрипторов типа
fd_set
, с помощью этих макросов устанавливаем и проверяем биты в наборе, а также можем присвоить его (как значение) другому набору дескрипторов с помощью оператора присваивания языка С.
ПРИМЕЧАНИЕ
Описываемый нами массив целых чисел, использующий по одному биту для каждого дескриптора, — это только один из возможных способов реализации функции 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
, определяемая при подключении заголовочного файла
<sys/select.h>
, является максимальным числом дескрипторов для типа данных
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
.
ПРИМЕЧАНИЕ
Две наиболее общих ошибки программирования при использовании функции select — это забыть добавить единицу к наибольшему номеру дескриптора и забыть, что наборы дескрипторов имеют тип «значение-результат». Вторая ошибка приводит к тому, что функция select вызывается с нулевым битом в наборе дескрипторов, когда мы думаем, что он установлен в единицу.
Возвращаемое этой функцией значение указывает общее число готовых дескрипторов во всех наборах дескрипторов. Если значение таймера истекает до того, как какой-нибудь из дескрипторов оказывается готов, возвращается нулевое значение. Возвращаемое значение -1 указывает на ошибку (которая может произойти, если, например, выполнение функции прервано перехваченным сигналом).