Термином
accept
, мы будем обозначать любой системный вызов, который может быть заблокирован навсегда. Такой системный вызов может никогда не завершиться. В эту категорию попадает большинство сетевых функций. Например, нет никакой гарантии, что вызов функции
accept
сервером когда-нибудь будет завершен, если нет клиентов, которые соединятся с сервером. Аналогично, вызов нашим сервером функции
read
(из
readline
) в листинге 5.2 никогда не возвратит управление, если клиент никогда не пошлет серверу строку для отражения. Другие примеры медленных системных вызовов — чтение и запись в случае программных каналов и терминальных устройств. Важным исключением является дисковый ввод-вывод, который обычно завершается возвращением управления вызвавшему процессу (в предположении, что не происходит фатальных аппаратных ошибок).
Основное применяемое здесь правило связано с тем, что когда процесс, блокированный в медленном системном вызове, перехватывает сигнал, а затем обработчик сигналов завершает работу, системный вызов
EINTR
.
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
для обработки завершенного дочернего процесса.
#include
pid_t wait(int *
pid_t waitpid(pid_t
Обе функции, и
wait
, и
waitpid
, возвращают два значения. Возвращаемое значение каждой из этих функций — это идентификатор завершенного дочернего процесса, а через указатель
statloc
передается статус завершения дочернего процесса (целое число). Для проверки статуса завершения можно вызвать три макроса, которые сообщают нам, что произошло с дочерним процессом: дочерний процесс завершен нормально, уничтожен сигналом или только приостановлен программой управления заданиями (job-control). Дополнительные макросы позволяют получить состояние выхода дочернего процесса, а также значение сигнала, уничтожившего или остановившего процесс. В листинге 15.8 мы используем макроопределения
WIFEXITED
и
WEXITSTATUS
.
Если у процесса, вызывающего функцию
wait
, нет завершенных дочерних процессов, но есть один или несколько выполняющихся, функция
wait
блокируется до тех пор, пока первый из дочерних процессов не завершится.