Книга: Основы программирования в Linux
Сигналы
Разделы на этой странице:
Сигнал — это событие, генерируемое системами UNIX и Linux в ответ на некоторую ситуацию, получив сообщение о котором процесс, в свою очередь, может предпринять какое-то действие. Мы применяем термин "возбуждать" (raise) для обозначения генерации сигнала и термин "захватывать" (catch) для обозначения получения или приема сигнала. Сигналы возбуждаются некоторыми ошибочными ситуациями, например нарушениями сегментации памяти, ошибками процессора при выполнении операций с плавающей точкой или некорректными командами. Они генерируются командной оболочкой и обработчиками терминалов для вызова прерываний и могут явно пересылаться от одного процесса к другому как способ передачи информации или коррекции поведения. Во всех этих случаях программный интерфейс один и тот же. Сигналы могут возбуждаться, улавливаться и соответственно обрабатываться или (по крайней мере, некоторые) игнорироваться.
Имена сигналов задаются с помощью включенного заголовочного файла signal.h. Они начинаются с префикса SIG
и включают приведенные в табл. 11.3 сигналы.
Таблица 11.3
Имя сигнала | Описание |
---|---|
SIGABORT |
*Процесс аварийно завершается |
SIGALRM |
Сигнал тревоги |
SIGFPE |
*Исключение операции с плавающей точкой |
SIGHUP |
Неожиданный останов или разъединение |
SIGILL |
*Некорректная команда |
SIGINT |
Прерывание терминала |
SIGKILL |
Уничтожение (не может быть перехвачен или игнорирован) |
SIGPIPE |
Запись в канал без считывателя |
SIGQUIT |
Завершение работы терминала |
SIGSEGV |
*Некорректный доступ к сегменту памяти |
SIGTERM |
Завершение, выход |
SIGUSR1 |
Сигнал 1, определенный пользователем |
SIGUSR2 |
Сигнал 2, определенный пользователем |
*Могут быть также предприняты действия, зависящие от конкретной реализации.
Если процесс получает один из этих сигналов без предварительной подготовки к его перехвату, процесс будет немедленно завершен. Обычно при этом создается файл с дампом ядра. Этот файл в текущем каталоге, названный core, представляет собой образ процесса, который может оказаться полезным при отладке.
К дополнительным относятся сигналы, приведенные в табл. 11.4.
Таблица 11.4
Имя сигнала | Описание |
---|---|
SIGCHLD |
Дочерний процесс остановлен или завершился |
SIGCONT |
Продолжить выполнение, если процесс был приостановлен |
SIGSTOP |
Остановить выполнение (не может захватываться или игнорироваться) |
SIGTSTP |
Сигнал останова, посылаемый с терминала |
SIGTTIN |
Фоновый процесс пытается читать |
SIGTTOU |
Фоновый процесс пытается писать |
Сигнал SIGCHLD
может быть полезен для управления дочерними процессами. По умолчанию он игнорируется. Остальные сигналы заставляют процессы, получившие их, остановиться, за исключением сигнала SIGCONT
, который вызывает возобновление процесса. Они применяются программами командной оболочки для контроля работы и редко используются в пользовательских программах.
Чуть позже мы рассмотрим более подробно первую группу сигналов. Пока же достаточно знать, что если командная оболочка и драйвер терминала нормально настроены, ввод символа прерывания (обычно от нажатия комбинации клавиш <Ctrl>+<C>) с клавиатуры приведет к отправке сигнала SIGINT
приоритетному процессу, т.е. программе, выполняющейся в данный момент. Это вызовет завершение программы, если в ней не предусмотрен перехват сигнала,
Если вы хотите отправить сигнал не текущей приоритетной задаче, а другому процессу, используйте команду kill
. Она принимает для отправки процессу в качестве необязательного параметра имя сигнала или его номер и PID (который, как правило, можно определить с помощью команды ps). Например, для отправки сигнала "останов или разъединение" командной оболочке, выполняющейся на другом терминале с PID 512, вы должны применить следующую команду:
$ kill -HUP 512
Удобный вариант команды kill
— команда killall
, которая позволяет отправить сигнал всем процессам, выполняющим конкретную команду. Не все системы UNIX поддерживают ее, но ОС Linux, как правило, поддерживает. Этот вариант полезен, когда вы не знаете PID процесса или хотите отправить сигнал нескольким разным процессам, выполняющим одну и ту же команду. Обычное применение — заставить программу inetd
перечитать параметры настройки. Для этого можно воспользоваться следующей командой:
$ killall -HUP inetd
Программы могут обрабатывать сигналы с помощью библиотечной функции signal
.
#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
Это довольно сложное объявление говорит о том, что signal
— это функция, принимающая два параметра, sig
и func
. Сигнал, который нужно перехватить или игнорировать, задается аргументом sig
. Функция, которую следует вызвать при получении заданного сигнала, содержится в аргументе func
. Эта функция должна принимать единственный аргумент типа int
(принятый сигнал) и иметь тип void
. Функция сигнала возвращает функцию того же типа, которая является предыдущим значением функции, заданной для обработки сигнала, или одно из двух специальных значений:
? SIG_IGN
— игнорировать сигнал;
? SIG_DFL
— восстановить поведение по умолчанию.
Пример сделает все понятным. В упражнении 11.7 вы напишете программу ctrlc.c, которая реагирует на нажатие комбинации клавиш <Ctrl>+<C> вместо обычного завершения выводом соответствующего сообщения. Повторное нажатие <Ctrl>+<C> завершает программу.
Упражнение 11.7. Обработка сигнала
Функция ouch
реагирует на сигнал, передаваемый в параметре sig
. Эта функция будет вызываться, когда возникнет сигнал. Она выводит сообщение и затем восстанавливает обработку сигнала по умолчанию для сигнала SIGINT (генерируется при нажатии комбинации клавиш <Ctrl>+<C>).
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void ouch(int sig) {
printf("OUCH! - I got signal %dn", sig);
(void)signal(SIGINT, SIG_DFL);
}
Функция main
должна взаимодействовать с сигналом SIGINT
, генерируемым при нажатии комбинации клавиш <Ctrl>+<C>. В остальное время она находится в бесконечном цикле, выводя один раз в секунду сообщение.
int main() {
(void)signal(SIGINT, ouch);
while(1) {
printf("Hello World!n");
sleep(1);
}
}
Ввод комбинации клавиш <Ctrl>+<C> (отображается как ^C
в следующем далее выводе) в первый раз заставляет программу отреагировать и продолжиться. Когда вы нажимаете <Ctrl>+<C> снова, программа завершается, т.к. сигнал SIGINT
вернул программе стандартное поведение, заставляющее ее завершиться.
$ ./ctrlcl
Hello World!
Hello World!
Hello World!
Hello World!
^C
OUCH! - I got signal 2
Hello World!
Hello World!
Hello World!
Hello World!
^C
$
Как видно из данного примера, функция обработки сигнала принимает один целочисленный параметр — номер сигнала, приводящий к вызову функции. Это удобно, если одна и та же функция применяется для обработки нескольких сигналов. В данном случае вы выводите значение SIGINT
, которое в этой системе оказывается равным 2. Не стоит полагаться на стандартные числовые значения сигналов, в новых программах всегда пользуйтесь именами сигналов.
Примечание
Вызывать из обработчика сигнала все функции, например, printf
, небезопасно. Удобный метод — использовать флаг, устанавливаемый в обработчике сигнала, и затем проверять этот флаг в функции main
и выводить сообщение, если нужно. В конце этой главы вы найдете список вызовов, которые можно безопасно применять в теле обработчиков сигналов.
Как это работает
Программа устроена так, что, когда вы задаете сигнал SIGINT
, нажимая комбинацию клавиш <Ctrl>+<C>, вызывает функцию ouch
. После того как функция прерывания ouch
завершится, программа продолжает выполняться, но восстанавливает реакцию на сигнал, принятую по умолчанию. (У разных версий UNIX, в особенности у потомков системы Berkeley UNIX, в течение многих лет сложилось разное поведение при получении сигналов. Если вы хотите восстановить поведение по умолчанию после возникновения сигнала, лучше всего запрограммировать его на конкретные действия.) Когда программа получает второй сигнал SIGINT
, она выполняет стандартное действие, приводящее к завершению программы.
Если вы хотите сохранить обработчик сигнала и продолжать реагировать на комбинацию клавиш <Ctrl>+<C>, вам придется восстановить его, вызвав функцию signal
еще раз. Это приведет к возникновению короткого промежутка времени, начиная с запуска функции прерывания и до момента восстановления обработчика сигнала, в течение которого сигнал не будет обрабатываться. Если второй сигнал будет получен в этот период, вопреки вашим желаниям программа может завершиться.
Примечание
Мы не рекомендуем вам пользоваться функцией signal
для перехвата сигналов. Мы включили ее в книгу, потому что она будет часто встречаться в более старых программах. Позже вы увидите sigaction
, более четко определенный и надежный интерфейс, который следует применять в новых программах.
Функция signal
возвращает предыдущее значение обработчика для заданного типа сигнала, если таковой есть, или в противном случае SIG_ERR
с установкой положительного значения в переменной errno
. Если задан неверный сигнал или делается попытка обработать сигнал, который не может быть перехвачен или игнорироваться, например SIGKILL
, переменной errno
присваивается значение EINVAL
.
- Звуковые сигналы BIOS
- 12.6. Сигналы реального времени
- 7.2.6.3. Системные демоны и традиционные сигналы
- Сигналы и шумы
- Световые сигналы
- Глава 10 Сигналы
- 10.3. Стандартные сигналы С: signal() и raise()
- 10.6. Сигналы POSIX
- 10.7. Сигналы для межпроцессного взаимодействия
- 10.8. Важные сигналы специального назначения
- 10.8.2. Сигналы, управляющие заданиями
- 10.9. Сигналы, передающиеся через fork() и exec()