Процесс может сам послать сигнал другому процессу. Чаще всего возникает необходимость завершить требуемый процесс с помощью сигнала
SIGTERM
или
SIGKILL
. [12] С помощью сигналов можно также передавать команды выполняющимся программам. Для этого существуют "пользовательские" сигналы
SIGUSR1
и
SIGUSR2
. Иногда в аналогичных целях применяется сигнал
SIGHUP
, с помощью которого можно заставить программу повторно прочитать свои файлы конфигурации.
12
В
чём между ними разница! Сигнал
SIGTERM
является запросам на завершение; процесс может его проигнорировать и продолжить свое выполнение.. Сигнал
SIGKILL
вызывает немедленное безусловное уничтожение процесса и не может быть обработан.
Функция
sigaction
определяет правила обработки указанного сигнала. Первый ее аргумент — это номер сигнала. Следующие два аргумента представляют собой указатели на структуру
sigaction
; первый из них регистрирует новый обработчик сигнала, а второй содержит описание предыдущего обработчика. Наиболее важным полем структуры
sigaction
является
sa_handler
. Оно может содержать одно из трех значений:
■
SIG_DFL
— выбор стандартного обработчика сигнала;
■
SIG_IGN
— игнорирование сигнала,
■ указатель на функцию обработки сигнала; эта функция должна принимать один параметр (номер сигнала) и возвращать значение типа
void
.
Поскольку сигнал может прийти в любой момент, он способен застать программу "врасплох" за выполнением критической операции, не подразумевающей прерывание. Такой операцией, к примеру, является обработка предыдущего сигнала. Отсюда правило: следует избегать операций ввода-вывода и вызовов большинства библиотечных и системных функций в обработчиках сигналов.
Обработчик должен выполнять минимум действий в ответ на получение сигнала и как можно быстрее возвращать управление в программу (или просто завершать ее работу). В большинстве случаев обработчик просто фиксирует факт поступления сигнала, а основная программа периодически проверяет, был ли сигнал, и реагирует должным образом.
Тем не менее возможность прерывания обработчика никогда нельзя исключать. Это очень сложная ситуация для диагностирования и отладки (и наглядный пример состояния гонки, о котором пойдет речь в разделе 4.4. "Синхронизация потоков и критические секции"). Необходимо внимательно следить за тем, что именно делается в обработчике.
Даже присвоение значения глобальной переменной несет потенциальную опасность, так как данная операция может занять два или три такта процессора, а за это время успеет прийти следующий сигнал, вследствие чего переменная окажется поврежденной. Если обработчик использует какую-то переменную в качестве флага поступления сигнала, она должна иметь специальный тип
sig_atomic_t
. Linux гарантирует, что операция присваивания значения такой переменной займет ровно один такт и не будет прервана. На самом деле тип
sig_atomic_t
в Linux эквивалентен типу
int
; более того, операции присваивания целочисленных переменных (32- и 16-разрядных) и указателей всегда атомарны. Использовать тип
sig_atomic_t
необходимо для того, чтобы программу можно было перенести в любую стандартную UNIX-систему.
В листинге 3.5 представлен шаблон программы, в которой функция-обработчик подсчитывает, сколько раз программа получает сигнал
SIGUSR1
.
Листинг 3.5. (sigusr1.c)
Корректное применение обработчика сигнала
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
sig_atomic_t sigusr1_count = 0;
void handler(int signal_number) {
++sigusr1_count;
}
int main {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = &handler;
sigaction(SIGUSR1, &sa, NULL);
/* далее идет основной текст. */
/* ... */
printf("SIGUSR1 was raised %d times\n", sigusr1_count);
return 0;
}
3.4. Завершение процесса
Обычно процесс завершается одним из двух способов: либо выполняющаяся программа вызывает функцию
exit
, либо функция
main
заканчивается. У каждого процесса есть код завершения — число, возвращаемое родительскому процессу. Этот код передается в качестве аргумента функции
exit
или возвращается функцией
main
.
Возможно также аварийное завершение процесса, в ответ на получение сигнала. Таковыми могут быть, например, упоминавшиеся выше сигналы
SIGBUS
,
SIGSEGV
и
SIGFPE
. Есть сигналы, явно запрашивающие прекращение работы процесса. В частности, сигнал
SIGINT
посылается, когда пользователь нажимает <Ctrl+C>. Сигнал
SIGTERM
посылается процессу командной
kill
по умолчанию. Если программа вызывает функцию
abort
, она посылает сама себе сигнал
SIGABRT
. Самый "могучий" из всех сигналов —
SIGKILL
: он приводит к безусловному уничтожению процесса и не может быть ни блокирован, ни обработан.
Любой сигнал можно послать с помощью команды
kill
, указав дополнительный флаг. Например, чтобы уничтожить процесс, послав ему сигнал
SIGKILL
, воспользуйтесь следующей командой:
% kill -KILL идентификатор_процесса
Для отправки сигнала из программы предназначена функция
kill
. Ее первым аргументом является идентификатор процесса. Второй аргумент — номер сигнала (стандартному поведению команды
kill
соответствует сигнал
SIGTERM
). Например, если переменная
child_pid
содержит идентификатор дочернего процесса, то следующая функция, вызываемая из родительского процесса, вызывает завершение работы потомка: