Цель данного примера — показать, что при написании сетевых программ, перехватывающих сигналы, необходимо получать информацию о прерванных системных вызовах и обрабатывать их. В этом специфичном для Solaris 2.5 примере функция signal
из стандартной библиотеки С не осуществляет автоматический перезапуск прерванного вызова, то есть флаг SA_RESTART
, установленный нами в листинге 5.5, не устанавливается функцией signal из системной библиотеки. Некоторые другие системы автоматически перезапускают прерванный системный вызов. Если мы запустим тот же пример в 4.4BSD, используя ее библиотечную версию функции signal
, ядро перезапустит прерванный системный вызов и функция accept
не возвратит ошибки. Одна из причин, по которой мы определяем нашу собственную версию функции signal
и используем ее далее, — решение этой потенциальной проблемы, возникающей в различных операционных системах (см. листинг 5.5).
Кроме того, мы всегда программируем явную функцию return
для наших обработчиков сигналов (см. листинг 5.6), даже если функция ничего не возвращает ( void
), чтобы этот оператор напоминал нам о возможности прерывания системного вызова при возврате из обработчика.
Обработка прерванных системных вызовов
Термином медленный системный вызов ( slow system call ), введенным при описании функции accept
, мы будем обозначать любой системный вызов, который может быть заблокирован навсегда. Такой системный вызов может никогда не завершиться. В эту категорию попадает большинство сетевых функций. Например, нет никакой гарантии, что вызов функции accept
сервером когда-нибудь будет завершен, если нет клиентов, которые соединятся с сервером. Аналогично, вызов нашим сервером функции read
(из readline
) в листинге 5.2 никогда не возвратит управление, если клиент никогда не пошлет серверу строку для отражения. Другие примеры медленных системных вызовов — чтение и запись в случае программных каналов и терминальных устройств. Важным исключением является дисковый ввод-вывод, который обычно завершается возвращением управления вызвавшему процессу (в предположении, что не происходит фатальных аппаратных ошибок).
Основное применяемое здесь правило связано с тем, что когда процесс, блокированный в медленном системном вызове, перехватывает сигнал, а затем обработчик сигналов завершает работу, системный вызов может возвратить ошибку EINTR
. Некоторые ядра автоматически перезапускают некоторые прерванные системные вызовы. Для обеспечения переносимости программ, перехватывающих сигналы (большинство параллельных серверов перехватывает сигналы SIGCHLD), следует учесть, что медленный системный вызов может возвратить ошибку EINTR. Проблемы переносимости связаны с написанными выше словами « могут » и « некоторые » и тем фактом, что поддержка флага POSIX SA_RESTART
не является обязательной. Даже если реализация поддерживает флаг SA_RESTART
, не все прерванные системные вызовы могут автоматически перезапуститься. Например, большинство реализаций, происходящих от Беркли, никогда автоматически не перезапускают функцию select
, а некоторые из этих реализаций никогда не перезапускают функции accept
и recvfrom
.
Чтобы обработать прерванный вызов функции accept
, мы изменяем вызов функции accept
, приведенной в листинге 5.1, в начале цикла for
следующим образом:
for (;;) {
clilen = sizeof(cliaddr);
if ((connfd = accept(listenfd, (SA*)&cliaddr, &clilen)) < 0) {
if (errno == EINTR)
continue; /* назад в for() */
else
err_sys("accept error");
}
Обратите внимание, что мы вызываем функцию accept
, а не функцию-обертку Accept
, поскольку мы должны обработать неудачное выполнение функции самостоятельно.
В этой части кода мы сами перезапускаем прерванный системный вызов. Это допустимо для функции accept
и таких функций, как read
, write
, select
и open
. Но есть функция, которую мы не можем перезапустить самостоятельно, — это функция connect
. Если она возвращает ошибку EINTR
, мы не можем снова вызвать ее, поскольку в этом случае немедленно возвратится еще одна ошибка. Когда функция connect прерывается перехваченным сигналом и не перезапускается автоматически, нужно вызвать функцию select
, чтобы дождаться завершения соединения (см. раздел 16.3).
5.10. Функции wait и waitpid
В листинге 5.7 мы вызываем функцию wait
для обработки завершенного дочернего процесса.
Читать дальше
Конец ознакомительного отрывка
Купить книгу