Литмир - Электронная Библиотека
Содержание  
A
A
ПРИМЕЧАНИЕ

На момент написания книги только некоторые системы поддерживали асинхронный ввод-вывод стандарта POSIX. Например, мы не уверены, что какие-либо системы поддерживают его для сокетов. Мы используем его только как пример для сравнения с моделью управляемого сигналом ввода-вывода.

Сравнение моделей ввода-вывода

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

recvfrom
на то время, пока данные копируются из ядра в буфер вызывающего процесса. Асинхронный ввод-вывод отличается от первых четырех моделей в обеих фазах.

UNIX: разработка сетевых приложений - img_50.png

Рис. 6.6. Сравнение моделей ввода-вывода

Сравнение синхронного и асинхронного ввода-вывода

POSIX дает следующие определения этих терминов:

■ Операция синхронного ввода-вывода блокирует запрашивающий процесс до тех пор, пока операция ввода-вывода не завершится.

■ Операция асинхронного ввода-вывода не вызывает блокирования запрашивающего процесса.

Используя эти определения, можно сказать, что первые четыре модели ввода- вывода — блокируемая, неблокируемая, модель мультиплексирования ввода-вывода и модель управляемого сигналом ввода-вывода — являются синхронными, поскольку фактическая операция ввода-вывода (функция

recvfrom
) блокирует процесс. Только модель асинхронного ввода-вывода соответствует определению асинхронного ввода-вывода.

6.3. Функция select

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

Например, мы можем вызвать функцию

select
и сообщить ядру, что возвращать управление нужно только когда наступит любое из следующих событий:

■ любой дескриптор из набора {1, 4, 5} готов для чтения;

■ любой дескриптор из набора {2, 7} готов для записи;

■ любой дескриптор из набора {1, 4} вызывает исключение, требующее обработки;

■ истекает 10,2 с.

Таким образом, мы сообщаем ядру, какие дескрипторы нас интересуют (готовые для чтения, готовые для записи или требующие обработки исключения) и как долго нужно ждать. Интересующие нас дескрипторы не ограничиваются сокетами: любой дескриптор можно проверить с помощью функции

select
.

ПРИМЕЧАНИЕ

Беркли-реализации всегда допускали мультиплексирование ввода-вывода с любыми дескрипторами. Система SVR3 ограничивала мультиплексирование ввода-вывода дескрипторами, которые являлись устройствами STREAMS (см. главу 31), но это ограничение было снято в SVR4.

#include <sys/select.h>

#include <sys/time.h>

int select(int <i>maxfdp1</i>, fd_set *<i>readset</i>, fd_set *<i>writeset</i>,

 fd_set *<i>exceptset</i>, const struct timeval *<i>timeout</i>);

<i>Возвращает: положительное число - счетчик готовых дескрипторов, 0 в случае тайм-аута, -1 в случае ошибки</i>

Описание этой функции мы начнем с последнего аргумента, который сообщает ядру, сколько следует ждать, пока один из заданных дескрипторов не будет готов. Структура

timeval
задает число секунд и микросекунд:

struct timeval {

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

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

};

С помощью этого аргумента можно реализовать три сценария:

1. Ждать вечно: завершать работу, только когда один из заданных дескрипторов готов для ввода-вывода. Для этого нужно определить аргумент

timeout
как пустой указатель.

2. Ждать в течение определенного времени: завершение будет происходить, когда один из заданных дескрипторов готов для ввода-вывода, но период ожидания ограничивается количеством секунд и микросекунд, заданным в структуре

timeval
, на которую указывает аргумент
timeout
.

3. Не ждать вообще: завершение происходит сразу же после проверки дескрипторов. Это называется опросом (polling). Аргумент

timeout
должен указывать на структуру
timeval
, а значение таймера (число секунд и микросекунд, заданных этой структурой) должно быть нулевым.

Ожидание в первых двух случаях обычно прерывается, когда процесс перехватывает сигнал и возвращается из обработчика сигнала.

ПРИМЕЧАНИЕ

Ядра реализаций, происходящих от Беркли, никогда автоматически не перезапускают функцию select [128, с. 527], в то время как ядра SVR4 перезапускают, если задан флаг SA_RESTART при установке обработчика сигнала. Это значит, что в целях переносимости мы должны быть готовы к тому, что функция select возвратит ошибку EINTR, если мы перехватываем сигналы.

Хотя структура

timeval
позволяет нам задавать значение с точностью до микросекунд, реальная точность, поддерживаемая ядром, часто значительно ниже. Например, многие ядра Unix округляют значение тайм-аута до числа, кратного 10 мс. Присутствует также и некоторая скрытая задержка: между истечением времени таймера и моментом, когда ядро запустит данный процесс, проходит некоторое время.

ПРИМЕЧАНИЕ

В некоторых системах при задании поля tv_sec более 100 млн с функция select завершается с кодом ошибки EINVAL Это, конечно, достаточно большое число (более трех лет), но факт остается фактом: структура timeval может содержать значения, не поддерживаемые функцией select.

Спецификатор

const
аргумента
timeout
означает, что данный аргумент не изменяется функцией
select
при ее возвращении. Например, если мы зададим предел времени, равный 10 с, и функция
select
возвратит управление до истечения этого времени с одним или несколькими готовыми дескрипторами или ошибкой
EINTR
, то структура
timeval
не изменится, то есть при завершении функции значение тайм-аута не станет равно числу секунд, оставшихся от исходных 10. Чтобы узнать количество неизрасходованных секунд, следует определить системное время до вызова функции
select
, а когда она завершится, определить его еще раз и вычесть первое значение из второго. Устойчивая программа должна учитывать тот факт, что системное время может периодически корректироваться администратором или демоном типа
ntpd
.

ПРИМЕЧАНИЕ

В современных системах Linux структура timeval изменяема. Следовательно, в целях переносимости будем считать, что структура timeval по возвращении становится неопределенной, и будем инициализировать ее перед каждым вызовом функции select. В POSIX указывается спецификатор const.

63
{"b":"225366","o":1}