Выбрать главу

Операционная система Linux посылает процессам сигналы в случае возникновения определенных ситуаций. Например, сигналы SIGBUS (ошибка на шине), SIGSEGV (нарушение сегментации) и SIGFPE (ошибка операции с плавающей запятой) могут быть посланы процессу, пытающемуся выполнить неправильную операцию. По умолчанию эти сигналы приводят к завершению процесса и созданию дампа оперативной памяти.

Процесс может сам послать сигнал другому процессу. Чаще всего возникает необходимость завершить требуемый процесс с помощью сигнала SIGTERM или SIGKILL.[12] С помощью сигналов можно также передавать команды выполняющимся программам. Для этого существуют "пользовательские" сигналы SIGUSR1 и SIGUSR2. Иногда в аналогичных целях применяется сигнал SIGHUP, с помощью которого можно заставить программу повторно прочитать свои файлы конфигурации.

Функция 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().

вернуться

12

В чём между ними разница! Сигнал SIGTERM является запросам на завершение; процесс может его проигнорировать и продолжить свое выполнение.. Сигнал SIGKILL вызывает немедленное безусловное уничтожение процесса и не может быть обработан.