По получении сигнала ядро останавливает процесс, где бы он ни был. Затем оно имитирует вызов процедуры обработчика сигнала, передавая ему номер сигнала в качестве ее единственного аргумента. Ядро устраивает все таким образом, что нормальный возврат из функции обработчика сигнала (либо посредством
return
, либо в результате выпадения из конца функции) передает управление в ту точку программы, в которой она находилась в момент появления сигнала.
Что происходит после обработки сигнала, когда тот же самый сигнал появится в следующий раз снова? Остается ли обработчик на том же месте? Или же он сбрасывается, и для сигнала используется действие по умолчанию? Ответ, по историческим причинам, «зависит от». В частности, стандарт С оставляет это на усмотрение реализации.
На практике V7 и традиционные системы System V, такие, как Solaris, устанавливают для сигнала действие по умолчанию.
Давайте рассмотрим простой обработчик сигнала в действии под Solaris. Следующая программа,
ch10-catchint.c
, перехватывает
SIGINT
. Обычно вы генерируете этот сигнал, набирая на клавиатуре CTRL-C.
1 /* ch10-catchint.c - перехват SIGINT, по крайней мере, однажды. */
2
3 #include <signal.h>
4 #include <string.h>
5 #include <unistd.h>
6
7 /* handler --- простой обработчик сигнала. */
8
9 void handler(int signum)
10 {
11 char buf[200], *cp;
12 int offset;
13
14 /* Пройти через это испытание , чтобы избежать fprintf. */
15 strcpy(buf, "handler: caught signal ");
16 cp = buf + strlen(buf); /* cp указывает на завершающий '\0' */
17 if (signum > 100) /* маловероятно */
18 offset = 3;
19 else if (signum > 10)
20 offset = 2;
21 else
22 offset = 1;
23 cp += offset;
24
25 *cp-- = '\0'; /* завершить строку */
26 while (signum >0) { /* work backwards, filling in digits */
27 *cp-- = (signum % 10) + '0';
28 signum /= 10;
29 }
30 strcat(buf, "\n");
31 (void)write(2, buf, strlen(buf));
32 }
33
34 /* main --- установить обработку сигнала и войти в бесконечный цикл */
35
36 int main(void)
37 {
38 (void)signal(SIGINT, handler);
39
40 for(;;)
41 pause; /* ждать сигнал, см. далее в главе */
42
43 return 0;
44 }
Строки 9–22 определяют функцию обработки сигнала (остроумно названную
handler
[106] ). Все, что эта функция делает, — выводит номер перехваченного сигнала и возвращается. Для вывода этого сообщения она выполняет множество ручной работы, поскольку
fprintf
не является «безопасной» для вызова из обработчика сигнала. (Вскоре это будет описано в разделе 10.4.6 «Дополнительные предостережения».)
106
Handler (англ.) — обработчик — Примеч. перев.
Функция
main
устанавливает обработчик сигнала (строка 38), а затем входит в бесконечный цикл (строки 40–41). Вот что происходит при запуске:
$ ssh solaris.example.com
/* Зарегистрироваться на доступной системе Solaris */
Last login: Fri Sep 19 04:33:25 2003 from 4.3.2.1.
Sun Microsystems Inc. SunOS 5.9 Generic May 2002
$ gcc ch10-catchint.c /* Откомпилировать программу */
Поскольку V7 и другие традиционные системы восстанавливают действие сигнала по умолчанию, поэтому когда вы хотите снова получить сигнал в будущем, функция обработчика должна немедленно переустановить саму себя:
. [107] На системах BSD обработчик сигнала после его возвращения остается на месте. Системы GNU/Linux следуют поведению BSD. Вот что происходит под GNU/Linux:
107
Изменение поведения было плохой мыслью, сильно критиковавшейся в свое время, но было слишком поздно. Изменение семантики определенного интерфейса всегда ведет к проблеме, как было в этом случае. Хотя это особенно относится к проектировщикам операционных систем, любой, кто разрабатывает библиотеки общего назначения, также должен помнить этот урок. — Примеч. автора.
$ ch10-catchint /* Запустить программу */
handler: caught signal 2 /* Набираем ^C, вызывается обработчик */
handler: caught signal 2 /* И снова... */
handler: caught signal 2 /* И снова! */
handler: caught signal 2 /* Помогите! */
handler: caught signal 2 /* Как нам это остановить?! */
Quit (core dumped) /* ^\, генерирует SIGQUIT. Bay */