Программирование на C

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

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

Сигнал

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

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

Сигналы определены в заголовочном файле сигнал.час как макроконстанта. Название сигнала начинается с «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 signal () (int signum, void (* функция) (интервал))

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

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

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

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

#включать
#включать
#включать
void sig_handler (int signum)
// Тип возврата функции-обработчика должен быть недействительным
printf ("\ nВнутренняя функция-обработчик \ n");

int main ()
сигнал (SIGINT, sig_handler); // Регистрируем обработчик сигнала
for (int i = 1 ;; i ++) // Бесконечный цикл
printf ("% d: внутри основной функции \ n", i);
сон (1); // Задержка на 1 секунду

возврат 0;

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

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

#включать
#включать
#включать
int main ()
сигнал (SIGINT, SIG_IGN); // Регистрируем обработчик сигнала для игнорирования сигнала
for (int i = 1 ;; i ++) // Бесконечный цикл
printf ("% d: внутри основной функции \ n", i);
сон (1); // Задержка на 1 секунду

возврат 0;

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

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

#включать
#включать
#включать
void sig_handler (int signum)
printf ("\ nВнутренняя функция-обработчик \ n");
сигнал (SIGINT, SIG_DFL); // Повторно регистрируем обработчик сигнала для действия по умолчанию

int main ()
сигнал (SIGINT, sig_handler); // Регистрируем обработчик сигнала
for (int i = 1 ;; i ++) // Бесконечный цикл
printf ("% d: внутри основной функции \ n", i);
сон (1); // Задержка на 1 секунду

возврат 0;

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

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

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

int поднять (int signum)

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

интервал убийства (pid_t pid, int signum)

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

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

#включать
#включать
void sig_handler (int signum)
printf ("Функция внутреннего обработчика \ n");

int main ()
сигнал (SIGUSR1, sig_handler); // Регистрируем обработчик сигнала
printf ("Внутри основной функции \ n");
поднять (SIGUSR1);
printf ("Внутри основной функции \ n");
возврат 0;

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

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

#включать
#включать
#включать
void sig_handler (int signum)
printf ("Функция внутреннего обработчика \ n");

int main ()
pid_t pid;
сигнал (SIGUSR1, sig_handler); // Регистрируем обработчик сигнала
printf ("Внутри основной функции \ n");
pid = getpid (); // Идентификатор процесса самого себя
убить (pid, SIGUSR1); // Отправляем SIGUSR1 самому себе
printf ("Внутри основной функции \ n");
возврат 0;

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

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

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

#включать
#включать
#включать
#включать
void sig_handler_parent (int signum)
printf ("Родитель: получил ответный сигнал от дочернего \ n");

void sig_handler_child (int signum)
printf ("Дочерний: получил сигнал от родителя \ n");
сон (1);
убить (getppid (), SIGUSR1);

int main ()
pid_t pid;
если ((pid = fork ())<0)
printf ("Ошибка вилки \ n");
выход (1);

/ * Дочерний процесс * /
else if (pid == 0)
сигнал (SIGUSR1, sig_handler_child); // Регистрируем обработчик сигнала
printf ("Ребенок: ждет сигнала \ n");
Пауза();

/ * Родительский процесс * /
еще
сигнал (SIGUSR1, sig_handler_parent); // Регистрируем обработчик сигнала
сон (1);
printf ("Родитель: отправка сигнала Дочернему \ n");
убить (pid, SIGUSR1);
printf ("Родитель: ждет ответа \ n");
Пауза();

возврат 0;

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

Заключение

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

Порты коммерческих игровых движков с открытым исходным кодом
Бесплатные игры с открытым исходным кодом и кроссплатформенные версии игрового движка можно использовать для игры как в старые, так и в некоторые из с...
Лучшие игры с командной строкой для Linux
Командная строка - не только ваш главный союзник при использовании Linux - она ​​также может быть источником развлечений, потому что вы можете использ...
Лучшие приложения для сопоставления геймпадов для Linux
Если вам нравится играть в игры на Linux с помощью геймпада вместо стандартной системы ввода с клавиатуры и мыши, для вас есть несколько полезных прил...