Как использовать обработчики сигналов на языке C?

How Use Signal Handlers C Language



В этой статье мы покажем вам, как использовать обработчики сигналов в Linux на языке C. Но сначала мы обсудим, что такое сигнал, как он будет генерировать некоторые общие сигналы, которые вы можете использовать в своей программе, а затем посмотрим, как различные сигналы могут обрабатываться программой во время выполнения программы. Итак, начнем.

Сигнал

Сигнал - это событие, которое генерируется для уведомления процесса или потока о наступлении важной ситуации. Когда процесс или поток получил сигнал, процесс или поток останавливает свои действия и предпринимает какие-то действия. Сигнал может быть полезен для межпроцессного взаимодействия.







Стандартные сигналы

Сигналы определены в заголовочном файле. signal.h как макроконстанта. Имя сигнала начинается с SIG, за которым следует краткое описание сигнала. Итак, каждый сигнал имеет уникальное числовое значение. Ваша программа всегда должна использовать имя сигналов, а не их номер. Причина в том, что номер сигнала может отличаться в зависимости от системы, но значение названий будет стандартным.



Макрос NSIG - общее количество определенных сигналов. Значение NSIG на единицу больше, чем общее количество определенных сигналов (все номера сигналов назначаются последовательно).



Ниже приведены стандартные сигналы:





Имя сигнала Описание
SIGHUP Прервите процесс. Сигнал SIGHUP используется для сообщения об отключении терминала пользователя, возможно, из-за потери удаленного соединения или зависания.
SIGINT Прервите процесс. Когда пользователь вводит символ INTR (обычно Ctrl + C), отправляется сигнал SIGINT.
SIGQUIT Выйти из процесса. Когда пользователь вводит символ QUIT (обычно Ctrl + ), отправляется сигнал SIGQUIT.
ТЮЛЕНЬ Незаконная инструкция. Когда делается попытка выполнить мусор или привилегированную инструкцию, генерируется сигнал SIGILL. Кроме того, SIGILL может генерироваться при переполнении стека или когда в системе возникают проблемы с запуском обработчика сигналов.
SIGTRAP Трассовая ловушка. Команда точки останова и другая команда ловушки генерируют сигнал SIGTRAP. Отладчик использует этот сигнал.
SIGABRT Прервать. Сигнал SIGABRT генерируется при вызове функции abort (). Этот сигнал указывает на ошибку, обнаруженную самой программой и сообщенную вызовом функции abort ().
SIGFPE Исключение с плавающей точкой. При возникновении фатальной арифметической ошибки генерируется сигнал SIGFPE.
SIGUSR1 и SIGUSR2 Сигналы SIGUSR1 и SIGUSR2 могут использоваться по вашему желанию. Полезно написать для них обработчик сигнала в программе, которая принимает сигнал, для простой связи между процессами.

Действие сигналов по умолчанию

Каждый сигнал имеет одно из следующих действий по умолчанию:

Срок: Процесс будет остановлен.
Основной: Процесс завершится и создаст файл дампа ядра.
Игн: Процесс проигнорирует сигнал.
Стоп: Процесс остановится.
Счет: Процесс не будет остановлен.



Действие по умолчанию можно изменить с помощью функции-обработчика. Действие по умолчанию для некоторых сигналов изменить нельзя. СИГКИЛЛ а также SIGABRT действие по умолчанию для сигнала нельзя изменить или проигнорировать.

Обработка сигналов

Если процесс получает сигнал, у процесса есть выбор действия для этого типа сигнала. Процесс может игнорировать сигнал, может указать функцию-обработчик или принять действие по умолчанию для этого типа сигнала.

  • Если указанное действие для сигнала игнорируется, то сигнал немедленно отбрасывается.
  • Программа может зарегистрировать функцию-обработчик, используя такую ​​функцию, как сигнал или подписание . Это называется обработчиком, улавливающим сигнал.
  • Если сигнал не был ни обработан, ни проигнорирован, выполняется его действие по умолчанию.

Мы можем обработать сигнал, используя сигнал или подписание функция. Здесь мы видим, как простейшие сигнал () функция используется для обработки сигналов.

intсигнал() (intподписать, пустота (*функция)(int))

В сигнал () позвонит функция функция, если процесс получает сигнал подписать . В сигнал () возвращает указатель на функцию функция в случае успеха или возвращает ошибку в errno и -1 в противном случае.

В функция указатель может иметь три значения:

  1. SIG_DFL : Это указатель на системную функцию по умолчанию. SIG_DFL () , заявленный в час заголовочный файл. Он используется для выполнения действия сигнала по умолчанию.
  2. SIG_IGN : Это указатель на функцию игнорирования системы. SIG_IGN () , заявленный в час заголовочный файл.
  3. Указатель на функцию обработчика, определяемую пользователем : Тип функции обработчика, определяемый пользователем: пустота (*) (число) , означает, что возвращаемый тип недействителен и один аргумент имеет тип int.

Пример базового обработчика сигналов

#включают
#включают
#включают
пустотаsig_handler(intподписать){

// Тип возврата функции-обработчика должен быть недействительным
printf (' пФункция внутреннего обработчика п');
}

intглавный(){
сигнал(SIGINT,sig_handler); // Регистрируем обработчик сигнала
для(intязнак равно1;;я++){ //Бесконечная петля
printf ('% d: внутри основной функции п',я);
спать(1); // Задержка на 1 секунду
}
возвращение 0;
}

На снимке экрана с выходными данными Example1.c мы видим, что в основной функции выполняется бесконечный цикл. Когда пользователь набирает Ctrl + C, выполнение основной функции останавливается и вызывается функция-обработчик сигнала. После завершения функции-обработчика выполнение основной функции возобновляется. Когда пользователь набирает Ctrl + , процесс завершается.

Пример игнорирования сигналов

#включают
#включают
#включают
intглавный(){
сигнал(SIGINT,SIG_IGN); // Регистрируем обработчик сигнала для игнорирования сигнала

для(intязнак равно1;;я++){ //Бесконечная петля
printf ('% d: внутри основной функции п',я);
спать(1); // Задержка на 1 секунду
}
возвращение 0;
}

Здесь функция-обработчик регистрируется для SIG_IGN () функция игнорирования действия сигнала. Итак, когда пользователь набрал Ctrl + C, SIGINT сигнал генерируется, но действие игнорируется.

Пример повторной регистрации обработчика сигнала

#включают
#включают
#включают

пустотаsig_handler(intподписать){
printf (' пФункция внутреннего обработчика п');
сигнал(SIGINT,SIG_DFL); // Повторно регистрируем обработчик сигнала для действия по умолчанию
}

intглавный(){
сигнал(SIGINT,sig_handler); // Регистрируем обработчик сигнала
для(intязнак равно1;;я++){ //Бесконечная петля
printf ('% d: внутри основной функции п',я);
спать(1); // Задержка на 1 секунду
}
возвращение 0;
}

На снимке экрана с выходными данными Example3.c мы видим, что когда пользователь впервые набирает Ctrl + C, вызывается функция-обработчик. В функции обработчика обработчик сигнала повторно регистрируется на SIG_DFL для действия сигнала по умолчанию. Когда пользователь нажимает Ctrl + C во второй раз, процесс завершается, что является действием по умолчанию для SIGINT сигнал.

Отправка сигналов:

Процесс также может явно посылать сигналы самому себе или другому процессу. Для отправки сигналов можно использовать функции raise () и kill (). Обе функции объявлены в заголовочном файле signal.h.

int поднимать (intподписать)

Функция raise (), используемая для отправки сигнала подписать вызывающему процессу (самому себе). Он возвращает ноль в случае успеха и ненулевое значение в случае неудачи.

intубийство(pid_t pid, intподписать)

Функция уничтожения, используемая для отправки сигнала подписать процессу или группе процессов, указанной пид .

Пример обработчика сигнала SIGUSR1

#включают
#включают

пустотаsig_handler(intподписать){
printf ('Внутренняя функция обработчика п');
}

intглавный(){
сигнал(SIGUSR1,sig_handler); // Регистрируем обработчик сигнала
printf ('Внутри основной функции п');
поднимать (SIGUSR1);
printf ('Внутри основной функции п');
возвращение 0;
}

Здесь процесс отправляет себе сигнал SIGUSR1 с помощью функции raise ().

Пример программы 'Рейз с убийством'

#включают
#включают
#включают
пустотаsig_handler(intподписать){
printf ('Внутренняя функция обработчика п');
}

intглавный(){
pid_t pid;
сигнал(SIGUSR1,sig_handler); // Регистрируем обработчик сигнала
printf ('Внутри основной функции п');
пидзнак равноGetpid(); // Идентификатор процесса самого себя
убийство(пид,SIGUSR1); // Отправляем SIGUSR1 самому себе
printf ('Внутри основной функции п');
возвращение 0;
}

Здесь процесс отправки SIGUSR1 сигнализировать самому себе, используя убийство() функция. getpid () используется для получения идентификатора самого процесса.

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

Связь родителей и детей с помощью сигналов

#включают
#включают
#включают
#включают
пустотаsig_handler_parent(intподписать){
printf (Родитель: получил ответный сигнал от дочернего п');
}

пустотаsig_handler_child(intподписать){
printf ('Дочерний: получил сигнал от родителя п');
спать(1);
убийство(Getppid(),SIGUSR1);
}

intглавный(){
pid_t pid;
если((пидзнак равновилка())<0){
printf ('Fork Failed п');
выход (1);
}
/ * Дочерний процесс * /
еще если(пид==0){
сигнал(SIGUSR1,sig_handler_child); // Регистрируем обработчик сигнала
printf (Ребенок: ждет сигнала п');
Пауза();
}
/ * Родительский процесс * /
еще{
сигнал(SIGUSR1,sig_handler_parent); // Регистрируем обработчик сигнала
спать(1);
printf ('Родитель: отправка сигнала ребенку п');
убийство(пид,SIGUSR1);
printf (Родитель: ждет ответа п');
Пауза();
}
возвращение 0;
}

Здесь, вилка() Функция создает дочерний процесс и возвращает ноль дочернему процессу и ID дочернего процесса родительскому процессу. Итак, pid был проверен, чтобы определить родительский и дочерний процессы. В родительском процессе он спит на 1 секунду, чтобы дочерний процесс мог зарегистрировать функцию обработчика сигналов и ждать сигнала от родителя. Через 1 секунду родительский процесс отправит SIGUSR1 сигнал дочернему процессу и дождитесь ответного сигнала от дочернего процесса. В дочернем процессе сначала он ожидает сигнала от родителя, а когда сигнал получен, вызывается функция-обработчик. Из функции-обработчика дочерний процесс отправляет другой SIGUSR1 сигнал родителю. Здесь getppid () функция используется для получения идентификатора родительского процесса.

Заключение

Сигнал в Linux - большая тема. В этой статье мы увидели, как обрабатывать сигнал с самого начала, а также узнали, как этот сигнал генерируется, как процесс может отправлять сигнал себе и другому процессу, как сигнал может использоваться для межпроцессного взаимодействия.