UNIX: разработка сетевых приложений
Листинг 5.5. Функция signal, вызывающая функцию POSIX sigaction
//lib/signal.c1 #include "unp.h"2 Sigfunc*3 signal(int signo, Sigfunc *func)4 {5 struct sigaction act, oact;6 act.sa_handler = func;7 sigemptyset(&act.sa_mask);8 act.sa_flags = 0;9 if (signo == SIGALRM) {10 #ifdef SA_INTERRUPT11 act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */12 #endif13 } else {14 #ifdef SA_RESTART15 act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */16 #endif17 }18 if (sigaction(signo, &act, &oact) < 0)19 return (SIG_ERR);20 return (oact.sa_handler);21 }Упрощение прототипа функции при использовании typedefОбычный прототип для функции2-3усложняется наличием вложенных скобок:signalvoid (*signal(int <i>signo</i>, void (*<i>func</i>)(int)))(int);Чтобы упростить эту запись, мы определяем тип
в нашем заголовочном файлеSigfuncследующим образом:unp.htypedef void Sigfunc(int);указывая тем самым, что обработчики сигналов — это функции с целочисленным аргументом, ничего не возвращающие (
). Тогда прототип функции выглядит следующим образом:voidSigfunc *signal(int <i>signo</i>, Sigfunc *<i>func</i>);Указатель на функцию, являющуюся обработчиком сигнала, — это второй аргумент функции и в то же время возвращаемое функцией значение.
Установка обработчикаЭлемент6структурыsa_handlerустанавливается равным аргументуsigactionфункцииfunc.signalУстановка маски сигнала для обработчикаPOSIX позволяет нам задавать набор сигналов, которые будут блокированы при вызове обработчика сигналов. Любой блокируемый сигнал не может быть доставлен процессу. Мы устанавливаем элемент7равным пустому набору. Это означает, что во время работы обработчика дополнительные сигналы не блокируются. POSIX гарантирует, что перехватываемый сигнал всегда блокирован, пока выполняется его обработчик.sa_maskУстановка флага SA_RESTARTФлаг8-17не является обязательным, и если он установлен, то системный вызов, прерываемый этим сигналом, будет автоматически снова выполнен ядром. (В продолжении нашего примера мы более подробно поговорим о прерванных системных вызовах.) Если перехватываемый сигнал не является сигналомSA_RESTART, мы задаем флагSIGALRM, если таковой определен. (Причина, по которой сигналSA_RESTARTобрабатывается отдельно, состоит в том, что обычно цель его генерации - ввести ограничение по времени в операцию ввода-вывода, как показано в листинге 14.2. В этом случае мы хотим, чтобы блокированный системный вызов был прерван сигналом.) Более ранние системы, особенно SunOS 4.x, автоматически перезапускают прерванный системный вызов по умолчанию и затем определяют флагSIGALRM. Если этот флаг задан, мы устанавливаем его при перехвате сигналаSA_INTERRUPT.SIGALRMВызов функции sigactionМы вызываем функцию18-20, а затем возвращаем старое действие сигнала как результат функцииsigaction.signalВ книге мы везде используем функцию
из листинга 5.5.signalСемантика сигналов POSIX
Сведем воедино следующие моменты, относящиеся к обработке сигналов в системе, совместимой с POSIX.
■ Однажды установленный обработчик сигналов остается установленным (в более ранних системах обработчик сигналов удалялся каждый раз по выполнении).
■ На время выполнения функции — обработчика сигнала доставляемый сигнал блокируется. Более того, любые дополнительные сигналы, заданные в наборе сигналов
, переданном функцииsa_maskпри установке обработчика, также блокируются. В листинге 5.5 мы устанавливаемsigactionравным пустому набору, что означает, что никакие сигналы, кроме перехватываемого, не блокируются.sa_mask■ Если сигнал генерируется один или несколько раз, пока он блокирован, то обычно после разблокирования он доставляется только один раз, то есть по умолчанию сигналы Unix не устанавливаются в очередь. Пример мы рассмотрим в следующем разделе. Стандарт POSIX реального времени 1003.1b определяет набор надежных сигналов, которые помещаются в очередь, но в этой книге мы их не используем.
■ Существует возможность выборочного блокирования и разблокирования набора сигналов с помощью функции
. Это позволяет нам защитить критическую область кода, не допуская перехватывания определенных сигналов во время ее выполнения.sigprocmask5.9. Обработка сигнала SIGCHLD
Назначение состояния зомби — сохранить информацию о дочернем процессе, чтобы родительский процесс мог ее впоследствии получить. Эта информация включает идентификатор дочернего процесса, статус завершения и данные об использовании ресурсов (время процессора, память и т.д.). Если у завершающегося процесса есть дочерний процесс в зомбированном состоянии, идентификатору родительского процесса всех зомбированных дочерних процессов присваивается значение 1 (процесс
), что позволяет унаследовать дочерние процессы и сбросить их (то есть процессinitбудет ждать (init) их завершения, благодаря чему будут удалены зомби). Некоторые системы Unix в столбцеwaitвыводят для зомбированных процессов значениеCOMMAND.<defunct>Обработка зомбированных процессов
Очевидно, что нам не хотелось бы оставлять процессы в виде зомби. Они занимают место в ядре, и в конце концов у нас может не остаться идентификаторов для нормальных процессов. Когда мы выполняем функцию
для дочерних процессов, необходимо с помощью функцииforkдождаться их завершения, чтобы они не превратились в зомби. Для этого мы устанавливаем обработчик сигналов для перехватывания сигналаwaitи внутри обработчика вызываем функциюSIGCHLD. (Функцииwaitиwaitмы опишем в разделе 5.10.) Обработчик сигналов мы устанавливаем с помощью вызова функцииwaitpid