Книга: Linux программирование в примерах

10.10. Резюме

10.10. Резюме

«Наша история до настоящего времени, эпизод III»

- Арнольд Роббинс (Arnold Robbins) -

• Интерфейсы обработки сигналов развились от простых, но подверженных состояниям гонок, до сложных, но надежных. К сожалению, множественность интерфейсов затрудняет их изучение по сравнению с другими API Linux/Unix.

• У каждого сигнала есть связанное с ним действие. Действие может быть одним из следующих: игнорирование сигнала; выполнение действия системы по умолчанию или вызов предоставленного пользователем обработчика. Действие системы по умолчанию, в свою очередь, является одним из следующих: игнорирование сигнала, завершение процесса; завершение процесса с созданием его образа; остановка процесса или возобновление процесса, если он остановлен.

• signal() и raise() стандартизованы ISO С. signal() управляет действиями для определенных сигналов; raise() посылает сигнал текущему процессу. Остаются ли обработчики сигналов установленными после вызова или сбрасываются для действия по умолчанию, зависит от реализации, signal() и raise() являются простейшими интерфейсами, для многих приложений их достаточно.

• POSIX определяет функцию bsd_signal(), которая подобна signal(), но гарантирует, что обработчик остается установленным.

• Действия, происходящие после возвращения из обработчика, варьируют в зависимости от системы. Традиционные системы (V7, Solaris, возможно, и другие) восстанавливают действие сигнала по умолчанию. На этих системах прерванный системный вызов возвращает -1, устанавливая в errno значение EINTR. Системы BSD оставляют обработчик установленным и возвращают -1 с errno, содержащим EINTR, лишь в случае, когда не было перемещения данных; в противном случае, системный вызов запускается повторно.

• GNU/Linux придерживается POSIX, который похож, но не идентичен с BSD. Если не было перемещения данных, системный вызов возвращает -1/EINTR. В противном случае он возвращает объем перемещенных данных. Поведение BSD «всегда повторный запуск» доступно через интерфейс sigaction(), но он не является действием по умолчанию.

• Обработчики сигналов, используемые с signal(), подвержены состояниям гонок. Внутри обработчиков сигналов должны использоваться исключительно переменные типа volatile sig_atomic_t. (В целях упрощения в некоторых из наших примеров мы не всегда следовали этому правилу.) Таким же образом, для вызова из обработчика сигналов безопасными являются лишь функции из табл. 10.2.

• Первоначальной попыткой создания надежных сигналов был API сигналов System V Release 3 (скопированный из BSD 4.0). Не используйте его в новом коде.

• POSIX API содержит множество компонентов:

  • маску сигналов процесса, перечисляющую текущие заблокированные сигналы;

  • тип sigset_t для представления масок сигналов, и функции sigfillset(), sigemptyset(), sigaddset(), sigdelset() и sigismember() для работы с ними;

  • функцию sigprocmask() для установки и получения маски сигналов процесса,

  • функцию sigpending() для получения набора ожидающих сигналов;

  • API sigaction() и struct sigaction во всем их великолепии.

Все эти возможности вместе используют блокирование сигналов и маску сигналов процесса для предоставления надежных сигналов. Более того, через различные флаги можно получить повторно запускаемые системные вызовы и более подходящие обработчики сигналов, которые получают большую информацию о причине, вызвавшей определенный сигнал (структура siginfo_t).

• Механизмами POSIX для посылки сигналов являются kill() и killpg(). Они отличаются от raise() в двух отношениях: (1) одни процесс может послать сигнал другому процессу или целой группе процессов (конечно, с проверкой прав доступа), и (2) посылка сигнала 0 ничего не посылает, но осуществляет проверку. Таким образом, эти функции предоставляют способ проверки наличия определенного процесса или группы процессов и возможность посылки ему (им) сигнала.

• Сигналы могут использоваться в качестве механизма IPC, хотя такой способ является плохим способом структурирования приложения, подверженным состояниям гонок. Если кто-то держит приставленным к вашей голове ружье, чтобы заставить вас работать таким способом, для правильной работы используйте тщательное блокирование сигналов и интерфейс sigaction().

• SIGALARM и системный вызов alarm() предоставляют низкоуровневый механизм для уведомления о прошествии определенного числа секунд, pause() приостанавливает процесс, пока не появятся какие-нибудь сигналы, sleep() использует их для помещения процесса в спящее состояние на заданный период времени: sleep() и alarm() не должны использоваться вместе. Сама pause() создает состояние гонки; вместо этого нужно использовать блокирование сигналов и sigsuspend().

• Сигналы управления заданиями реализуют управление заданиями для оболочки. Большую часть времени следует оставлять их с установленными действиями по умолчанию, но полезно понимать, что иногда имеет смысл их перехватывать.

• Перехват SIGCHLD позволяет родителю узнать, что делает порожденный им процесс. Использование 'signal(SIGCHLD, SIG_IGN)' (или sigaction() с SA_NOCLDWAIT) вообще игнорирует потомков. Использование sigaction() с SA_NOCLDSTOP предоставляет уведомления лишь о завершении. В последнем случае, независимо от того, заблокирован SIGCHLD или нет, обработчики сигналов для SIGCHLD должны быть готовы немедленно обработать несколько потомков. Наконец, использование sigaction() без SA_NOCLDSTOP с обработчиком сигналов с тремя аргументами дает вам причину получения сигнала.

• После fork() положение сигналов в порожденном процессе остается тем же самым, за исключением сброса ожидающих сигналов и установленных интервалов таймера. После exec() положение несколько более сложно — в сущности, все, что может быть оставлено, остается; для всего остального восстанавливаются значения по умолчанию.

Оглавление книги


Генерация: 0.078. Запросов К БД/Cache: 0 / 2
поделиться
Вверх Вниз