/* Обработка прерываний и отсоединений. Упрощена для представления */
static void sighandler(int sig) {
signal(sig, SIG_IGN); /* Отныне этот сигнал игнорировать */
cleanup; /* Очистка после себя */
signal(sig, SIG_DFL); /* Восстановление действия по умолчанию */
raise(sig); /* Повторно отправить сигнал */
}
Установка действия
SIG_IGN
гарантирует, что все последующие появляющиеся сигналы
SIGINT
не повлияют на продолжающийся процесс очистки. Когда функция
cleanup
завершит работу, восстановление действия
SIG_DFL
позволяет системе сделать снимок образа процесса, если это нужно возникшему сигналу. Вызов
raise
восстанавливает сигнал. Затем восстановленный сигнал вызывает действие по умолчанию, которое, скорее всего, завершит программу. (Далее в этой главе мы полностью покажем обработчик сигнала
(см. раздел 4.3 «Определение ошибок») указывает, что системный вызов был прерван. Хотя с этим значением ошибки может завершаться большое количество системных вызовов, двумя наиболее значительными являются
Предположим, что система успешно прочла (и заполнила) часть буфера, когда возник
SIGINT
. Системный вызов
read
еще не вернулся из ядра в программу, но ядро решает, что оно может доставить сигнал. Вызывается
handler
, запускается и возвращается в середину
read
. Что возвратит
read
?
В былые времена (V7, более ранние системы System V)
read
возвратила бы -1 и установила бы
errno
равным
EINTR
. Не было способа сообщить, что данные были переданы. В данном случае V7 и System V действуют, как если бы ничего не случилось: не было перемещений данных в и из буфера пользователя, и смещение файла не было изменено. BSD 4.2 изменила это. Были два случая:
Медленные устройства
«Медленное устройство» является в сущности терминалом или почти всяким другим устройством, кроме обычного файла. В этом случае
read
могла завершиться с ошибкой
EINTR
, лишь если не было передано никаких данных, когда появился сигнал. В противном случае системный вызов был бы запущен повторно, и
read
возвратилась бы нормально.
Обычные файлы
Системный вызов был бы запущен повторно В этом случае
read
вернулась бы нормально; возвращенное значение могло быть либо числом запрошенных байтов, либо числом действительно прочитанных байтов (как в случае чтения вблизи конца файла).
Поведение BSD несомненно полезно; вы всегда можете сказать, сколько данных было прочитано.
Поведение POSIX сходно, но не идентично первоначальному поведению BSD. POSIX указывает, что
лишь в случае появления сигнала до начала перемещения данных. Хотя POSIX ничего не говорит о «медленных устройствах», на практике это условие проявляется именно на них.
108
Хотя мы описываем
read
, эти правила применяются ко всем системным вызовам, которые могут завершиться с ошибкой
EINTR
, как, например, семейство функций
wait
— Примеч. автора.
В противном случае, если сигнал прерывает частично выполненную
read
, возвращенное значение является числом уже прочитанных байтов. По этой причине (а также для возможности обработки коротких файлов) всегда следует проверять возвращаемое
read
значение и никогда не предполагать, что прочитано все запрошенное количество байтов. (Функция POSIX API
sigaction
, описанная позже, позволяет при желании получить поведение повторно вызываемых системных вызовов BSD.)
10.4.4.1. Пример: GNU Coreutils
safe_read
и
safe_write
Для обработки случая EINTR в традиционных системах GNU Coreutils использует две функции,
safe_read
и
safe_write
. Код несколько запутан из-за того, что один и тот же файл за счет включения #include и макросов реализует обе функции. Из файла
lib/safe-read.c
в дистрибутиве Coreutils:
1 /* Интерфейс read и write для .повторных запусков после прерываний.