12.1.4. Сигналы и системные вызовы
12.1.4. Сигналы и системные вызовы
Часто сигналы доставляются процессу, который находится состоянии ожидания наступления некоторого внешнего события. Например, текстовый редактор часто ожидает завершения read(), чтобы возвратить ввод терминала. Когда системный администратор посылает процессу сигнал SIGTERM (нормальный сигнал, посылаемый командой kill, позволяющий процессу завершиться чисто), то процесс может обработать его, как описано ниже.
1. Не пытаться перехватить сигнал и быть прерванным ядром (обработка SIGTERM по умолчанию). Это оставляет пользовательский терминал в нестандартной конфигурации, затрудняя ему продолжение работы.
2. Перехватить сигнал, очистить терминал с помощью обработчика этого сигнала, затем выйти. Хотя это кажется привлекательным, в сложных программах трудно написать такой обработчик, который знал бы достаточно о том, что делает программа в момент прерывания, чтобы правильно выполнить очистку.
3. Перехватить сигнал, установить флаг, обозначающий, что сигнал получен, и каким-то образом обеспечить выход из блокирующего системного вызова (в данном случае read()) с индикацией ошибки — в знак того, что произошло что-то необычное. Нормальный порядок выполнения затем должен проверить флаг и обработать его соответствующим образом.
Поскольку последний вариант выглядит намного чище и легче остальных, оригинальная реализация сигналов заставляет медленные системные вызовы возвратить EINTR, когда они прерываются сигналом, в то время как быстрые системные вызовы завершаются перед тем, как сигнал будет доставлен.
Медленные системные вызовы требуют неопределенного времени для своего завершения. Системные вызовы, которые для завершения своей работы ожидают непредсказуемых ресурсов, таких как другие процессы, сетевые данные либо действия со стороны человека, рассматриваются как медленные. Семейство системных вызовов wait(), например, не возвращают управление до тех пор, пока дочерние процессы не завершатся. Поскольку невозможно узнать, насколько долго продлится это ожидание, считается, что wait() — медленный системный вызов. Системные вызовы доступа к файлам рассматриваются как медленные, если они обращаются к медленным файлам, и быстрые — если к быстрым файлам[58].
Обязанностью процесса является обработка EINTR и перезапуск системных вызовов в случае необходимости. Хотя это обеспечивает всю функциональность, которая требуется людям, намного сложнее написать код, который обрабатывает сигналы. Всякий раз когда read() вызывается на медленном файловом дескрипторе, код должен проверять его возврат на равенство EINTR, и перезапускать вызов, либо он не будет делать то, что ожидается.
Чтобы "упростить" ситуацию, 4.2BSD автоматически перезапускает такие системные вызовы (особенно read() и write()). Поэтому для большинства операций программы более не должны беспокоиться об EINTR, поскольку выполнение системных вызовов продолжится после того, как процесс обработает сигнал. В последних версиях Unix изменен перечень системных вызовов, которые автоматически перезапускаются, a 4.3BSD позволяет вам выбрать, какие системные вызовы перезапускать. Стандарт обработки сигналов POSIX не указывает, какое поведение должно применяться, но все популярные системы согласны в том, как обрабатывать этот случай. По умолчанию системные вызовы не перезапускаются, но для каждого сигнала процесс может установить флаг, который указывает, что система должна перезапускать системные вызовы, прерванные этим сигналом.