10.6.4. Перехват сигналов: sigaction()

10.6.4. Перехват сигналов: sigaction()

Наконец мы готовы взглянуть на функцию sigaction(). Эта функция сложна, и мы намеренно опускаем множество деталей, которые предназначены для специального использования. Стандарт POSIX и справочная страница sigaction(2) предоставляют все подробности, хотя вы должны тщательно прочесть и то, и другое, чтобы полностью все усвоить.

#include <signal.h> /* POSIX */

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

Аргументы следующие:

int signum

Интересующий сигнал, как в случае с другими функциями обработки сигналов.

const struct sigaction *act

Определение нового обработчика для сигнала signum.

struct sigaction *oldact

Определение текущего обработчика. Если не NULL, система до установки *act заполняет *oldact. *act может быть NULL, в этом случае *oldact заполняется, но больше ничего не меняется.

Таким образом, sigaction() и устанавливает новый обработчик, и получает старый за одно действие. struct sigaction выглядит следующим образом.

/* ПРИМЕЧАНИЕ: Порядок в структуре может варьировать. Могут быть

   также и другие поля! */

struct sigaction {

 sigset_t sa_mask; /* Дополнительные сигналы для блокирования */

 int sa_flags;     /* Контролирует поведение */

 void (*sa_handler)(int);

  /* Может образовать объединение с sa_sigaction */

 void (*sa_sigaction)(int, siginfo_t*, void*);

  /* Может образовать объединение с sa_handler */

}

Поля следующие:

sigset_t sa_mask

Набор дополнительных сигналов для блокирования при запуске функции обработчика. Таким образом, когда вызывается обработчик, общий набор заблокированных сигналов является объединением сигналов в маске процесса, сигналов в act->mask и, если SA_NODEFER сброшен, signum.

int sa_flags

Флаги, контролирующие обработку сигнала ядром. См. обсуждение далее.

void (*sa_handler)(int)

Указатель на «традиционную» функцию обработчика. У нее такой же прототип (возвращаемый тип и список параметров), как у функции обработчика для signal(), bsd_signal() и sigset().

void (*sa_sigaction)(int, siginfo_t*, void*)

Указатель на функцию обработчика «нового стиля». Функция принимает три аргумента, которые вскоре будут описаны.

Которая из функций act->sa_handler и act->sa_sigaction используется, зависит от флага SA_SIGINFO в act->sa_flags. Когда имеется, используется act->sa_sigaction; в противном случае используется act->sa_handler. Как POSIX, так и справочная страница GNU/Linux указывают, что эти два поля могут перекрываться в памяти (т. е. быть частью union). Таким образом, никогда не следует использовать оба поля в одной и той же struct sigaction.

Поле sa_flags составляется с помощью побитового ИЛИ значений одного или более флагов, перечисленных в табл. 10.3.

Таблица 10.3. Значения флагов для sa_flags

Флаг Значение SA_NOCLDSTOP Этот флаг имеет смысл лишь для SIGCHLD. Когда он установлен, родитель не получает сигнал при остановке порожденною процесса сигналами SIGSTOP, SIGTSTP, SIGTTIN или SIGTTOU. Эти сигналы обсуждаются позже, в разделе 10.8.2 SA_NOCLDWAIТ Этот флаг имеет смысл лишь для SIGCHLD. Его поведение сложно. Мы отложим объяснение на потом, см. раздел 10.8.3 SA_NODEFER Обычно данный сигнал блокируется, когда вызывается обработчик сигнала. Когда установлен один из этих флагов, данный сигнал не блокируется при запуске обработчика SA_NODEFER является официальным именем POSIX данного флага (которое и следует использовать) SA_NOMASK Альтернативное имя для SA_NODEFER[110] SA_SIGINFO Обработчик сигнала принимает три аргумента. Как упоминалось, при данном установленном флаге должно использоваться поле sa_sigaction вместо sa_handler. SA_ONSTACK Это продвинутая возможность. Обработчики сигналов могут вызываться с использованием предоставленной пользователем памяти в качестве «альтернативного стека сигнала». Эта память даётся ядру для подобного использования посредством sigaltstack() (см. sigaltstack(2)). Эта особенность больше не описывается в данной книге SA_RESETHAND Этот флаг обеспечивает поведение V7: после вызова обработчика восстанавливается действие сигнала по умолчанию. Официальным именем POSIX флага (которое следует использовать) является SA_RESETHAND SA_ONESHOT Альтернативное имя для SA_RESETHAND. SA_RESTART Этот флаг предоставляет семантику BSD: системные вызовы, которые могут завершиться с ошибкой EINTR и которые получают этот сигнал, запускаются повторно.

Когда в act->sa_flags установлен флаг SA_SIGINFO, поле act->sa_sigaction является указателем на функцию, объявленную следующим образом:

void action_handler(int sig, siginfo_t *info, void *context) {

 /* Здесь тело обработчика */

}

Структура siginfo_t предоставляет изобилие сведений о сигнале:

/* Определение POSIX 2001. Действительное содержание может на разных системах быть разным. */

typedef struct {

 int si_signo;  /* номер сигнала */

 int si_errno;  /* значение <errno.h> при ошибке */

 int si_code;   /* код сигнала; см. текст */

 pid_t si_pid;  /* ID процесса, пославшего сигнал */

 uid_t si_uid;  /* настоящий UID посылающего процесса */

 void *si_addr; /* адрес вызвавшей ошибку инструкции */

 int si_status; /* значение завершения, может включать death-by-signal */

 long si_band;  /* связывающее событие для SIGPOLL/SIGIO */

 union sigval si_value; /* значение сигнала (расширенное) */

} siginfo_t;

Поля si_signo, si_code и si_value доступны для всех сигналов. Другие поля могут быть членами объединения, поэтому должны использоваться лишь для тех сигналов, для которых они определены. В структуре siginfo_t могут быть также и другие поля.

Почти все поля предназначены для расширенного использования. Все подробности содержатся в стандарте POSIX и справочной странице sigaction(2). Однако, мы можем описать простое использование поля si_code.

Для SIGBUS, SIGCHLD, SIGFPE, SIGILL, SIGPOLL, SIGSEGV и SIGTRAP поле si_code может принимать любое из набора предопределенных значений, специфичных для каждого сигнала, указывая на причину появления сигнала. Откровенно говоря, детали несколько чрезмерны; повседневному коду на самом деле нет необходимости иметь с ними дела (хотя позже мы рассмотрим значения для SIGCHLD). Для всех остальных сигналов член si_code имеет одно из значений из табл. 10.4.

Таблица 10.4. Значения происхождения сигнала для si_code

Значение Только GLIBC Смысл SI_ASYNCIO Асинхронный ввод/вывод завершен (расширенный). SI_KERNEL ? Сигнал послан ядром. SI_MESGQ Состояние очереди сообщений изменилось (расширенный.) SI_QUEUE Сигнал послан из sigqueue() (расширенный). SI_SIGIO ? SIGIO поставлен в очередь (расширенный). SI_TIMER Время таймера истекло SI_USER Сигнал послан функцией kill(). raise() и abort() также могут его вызвать, но это не обязательно.

В особенности полезно значение SI_USER; оно позволяет обработчику сигнала сообщить, был ли сигнал послан функциями raise() или kill() (описываются далее). Вы можете использовать эту информацию, чтобы избежать повторного вызова raise() или kill().

Третий аргумент обработчика сигнала с тремя аргументами, void *context, является расширенной возможностью, которая больше не обсуждается в данной книге.

Наконец, чтобы увидеть sigaction() в действии, исследуйте полный исходный код обработчика сигнала для sort.c:

2074 static void

2075 sighandler(int sig)

2076 {

2077 #ifndef SA_NOCLDSTOP /* В системе старого стиля... */

2078  signal(sig, SIG_IGN); /* - для игнорирования sig используйте signal()*/

2079 #endif - /* В противном случае sig автоматически блокируется */

2080

2081  cleanup(); /* Запуск кода очистки */

2082

2083 #ifdef SA_NOCLDSTOP /* В системе в стиле POSIX... */

2084  {

2085   struct sigaction sigact;

2086

2087   sigact.sa_handler = SIG_DFL; /* - Установить действие по умолчанию */

2088   sigemptyset(&sigact.sa_mask); /* - Нет дополнительных сигналов для блокирования */

2089   sigact.sa_flags = 0; /* - Специальные действия не предпринимаются */

2090   sigaction(sig, &sigact, NULL); /* - Поместить на место */

2091  }

2092 #else /* На системе в старом стиле... */

2093  signal(sig, SIG_DFL); /* - Установить действие по умолчанию */

2094 #endif

2095

2096  raise(sig); /* Повторно послать сигнал */

2097 }

Вот код в main(), который помещает обработчик на свое место:

2214 #ifdef SA_NOCLDSTOP /* На системе POSIX... */

2215 {

2216  unsigned i;

2217  sigemptyset(&caught_signals);

2218  for (i = 0; i < nsigs; i++) /* - Блокировать все сигналы */

2219   sigaddset(&caught_signals, sigs[i]);

2220  newact.sa_handler = sighandler; /* - Функция обработки сигнала */

2221  newact.sa_mask = caught_signals; /* - Установить для обработчика маску сигналов процесса */

2222  newact.sa_flags =0; /* - Особых флагов нет */

2223 }

2224 #endif

2225

2226 {

2227  unsigned i;

2228  for (i = 0; i < nsigs; i++) /* Для всех сигналов... */

2229  {

2230   int sig = sigs[i];

2231 #ifdef SA_NOCLDSTOP

2232   sigaction(sig, NULL, &oldact); /* - Получить старый обработчик */

2233   if (oldact.sa_handler != SIG_IGN) /* - Если этот сигнал не игнорируется */

2234    sigaction(sig, &newact, NULL); /* - Установить наш обработчик */

2235 #else

2236   if (signal(sig, SIG_IGN) != SIG_IGN)

2237    signal(sig, sighandler); /* - Та же логика со старым API */

2238 #endif

2239  }

2240 }

Мы заметили, что строки 2216–2219 и 2221 могут быть замещены одним вызовом: sigfillset(&newact.sa_mask);

Мы не знаем, почему код написан именно таким способом.

Интерес представляют также строки 2233–2234 и 2236–2237, которые показывают правильный способ проверки того, игнорируется ли сигнал, и для установки обработчика лишь в том случае, если сигнал не игнорируется.

ЗАМЕЧАНИЕ. Функции API sigaction() и signal() не должны использоваться вместе для одного и того же сигнала. Хотя POSIX идет на большие длинноты, чтобы сначала сделать возможным использование signal(), получить struct sigaction, представляющую диспозицию signal(), и восстановить ее, все равно это плохая мысль. Код будет гораздо проще читать, писать и понимать, если вы используете одну функцию или другую взаимоисключающим образам