Учебники

Межпроцессное взаимодействие — сигналы

Сигнал — это уведомление процесса, указывающее на возникновение события. Сигнал также называется программным прерыванием и не может предсказать его возникновение, поэтому он также называется асинхронным событием .

Сигнал может быть указан с помощью номера или имени, обычно имена сигналов начинаются с SIG. Доступные сигналы можно проверить с помощью команды kill –l (l для списка имен сигналов), которая выглядит следующим образом:

Сигнал

Всякий раз, когда сигнал повышается (программно или сгенерированный системой сигнал), выполняется действие по умолчанию. Что если вы не хотите выполнять действие по умолчанию, но хотите выполнить свои собственные действия при получении сигнала? Возможно ли это для всех сигналов? Да, возможно обрабатывать сигнал, но не для всех сигналов. Что если вы хотите игнорировать сигналы, возможно ли это? Да, можно игнорировать сигнал. Игнорирование сигнала не подразумевает ни выполнения действия по умолчанию, ни обработки сигнала. Можно игнорировать или обрабатывать почти все сигналы. Сигналы, которые нельзя игнорировать или обрабатывать / перехватывать, это SIGSTOP и SIGKILL.

Таким образом, действия, выполняемые для сигналов, следующие:

  • Действие по умолчанию
  • Обрабатывать сигнал
  • Игнорировать сигнал

Как обсуждено, сигнал может быть обработан, изменяя выполнение действия по умолчанию. Обработка сигнала может быть выполнена любым из двух способов, т. Е. С помощью системных вызовов, signal () и sigaction ().

#include <signal.h>

typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);

Сигнал системного вызова () будет вызывать зарегистрированный обработчик при генерации сигнала, как указано в signum. Обработчик может быть одним из SIG_IGN (игнорирование сигнала), SIG_DFL (установка сигнала обратно на механизм по умолчанию) или определяемым пользователем обработчиком сигнала или адресом функции.

Этот системный вызов в случае успеха возвращает адрес функции, которая принимает целочисленный аргумент и не имеет возвращаемого значения. Этот вызов возвращает SIG_ERR в случае ошибки.

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

#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)

Этот системный вызов используется для проверки или изменения действия сигнала. Если действие не является нулевым, из действия устанавливается новое действие для сигнала signum. Если oldact не имеет значение null, предыдущее действие сохраняется в oldact.

Структура sigaction содержит следующие поля:

Поле 1 — обработчик, упомянутый либо в sa_handler, либо в sa_sigaction.

void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);

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

Обработчик для sa_sigaction указывает номер сигнала в качестве первого аргумента, указатель на структуру siginfo_t в качестве второго аргумента и указатель на пользовательский контекст (для получения дополнительной информации проверьте getcontext () или setcontext ()) в качестве третьего аргумента.

Структура siginfo_t содержит информацию о сигнале, такую ​​как номер сигнала, который должен быть доставлен, значение сигнала, идентификатор процесса, реальный идентификатор пользователя процесса отправки и т. Д.

Поле 2 — набор сигналов для блокировки.

int sa_mask;

Эта переменная указывает маску сигналов, которые должны быть заблокированы во время выполнения обработчика сигналов.

Поле 3 — Специальные флаги.

int sa_flags;

В этом поле указывается набор флагов, которые изменяют поведение сигнала.

Поле 4 — Восстановить обработчик.

void (*sa_restorer) (void);

Этот системный вызов возвращает 0 в случае успеха и -1 в случае сбоя.

Давайте рассмотрим несколько примеров программ.

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

/* signal_fpe.c */
#include<stdio.h>

int main() {
   int result;
   int v1, v2;
   v1 = 121;
   v2 = 0;
   result = v1/v2;
   printf("Result of Divide by Zero is %d\n", result);
   return 0;
}

Шаги компиляции и выполнения

Floating point exception (core dumped)

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

Теперь давайте изменим код для обработки этого конкретного сигнала с помощью системного вызова signal ().

/* signal_fpe_handler.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

void handler_dividebyzero(int signum);

int main() {
   int result;
   int v1, v2;
   void (*sigHandlerReturn)(int);
   sigHandlerReturn = signal(SIGFPE, handler_dividebyzero);
   if (sigHandlerReturn == SIG_ERR) {
      perror("Signal Error: ");
      return 1;
   }
   v1 = 121;
   v2 = 0;
   result = v1/v2;
   printf("Result of Divide by Zero is %d\n", result);
   return 0;
}

void handler_dividebyzero(int signum) {
   if (signum == SIGFPE) {
      printf("Received SIGFPE, Divide by Zero Exception\n");
      exit (0);
   } 
   else
      printf("Received %d Signal\n", signum);
      return;
}

Шаги компиляции и выполнения

Received SIGFPE, Divide by Zero Exception

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

Теперь давайте возьмем другую программу для демонстрации обработки и игнорирования сигнала.

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

Чтобы продолжить выполнение остановленного процесса, отправьте SIGCONT этому конкретному процессу. Вы также можете выполнить команды fg (передний план) или bg (фон), чтобы продолжить выполнение. Здесь команды только перезапустят выполнение последнего процесса. Если остановлено более одного процесса, возобновляется только последний процесс. Если вы хотите возобновить ранее остановленный процесс, возобновите работу (используя fg / bg) вместе с номером работы.

Следующая программа используется для повышения сигнала SIGSTOP с помощью функции повысить (). Сигнал SIGSTOP также может быть сгенерирован нажатием клавиши CTRL + Z (Control + Z). После выдачи этого сигнала программа прекратит выполнение. Отправьте сигнал (SIGCONT), чтобы продолжить выполнение.

В следующем примере мы возобновляем остановленный процесс командой fg.

/* signal_raising.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

int main() {
   printf("Testing SIGSTOP\n");
   raise(SIGSTOP);
   return 0;
}

Шаги компиляции и выполнения

Testing SIGSTOP
[1]+ Stopped ./a.out
./a.out

Теперь улучшите предыдущую программу, чтобы продолжить выполнение остановленного процесса, выдав SIGCONT из другого терминала.

/* signal_stop_continue.c */
#include<stdio.h>
#include<signal.h>
#include <sys/types.h>
#include <unistd.h>

void handler_sigtstp(int signum);

int main() {
   pid_t pid;
   printf("Testing SIGSTOP\n");
   pid = getpid();
   printf("Open Another Terminal and issue following command\n");
   printf("kill -SIGCONT %d or kill -CONT %d or kill -18 %d\n", pid, pid, pid);
   raise(SIGSTOP);
   printf("Received signal SIGCONT\n");
   return 0;
}

Шаги компиляции и выполнения

Testing SIGSTOP
Open Another Terminal and issue following command
kill -SIGCONT 30379 or kill -CONT 30379 or kill -18 30379
[1]+ Stopped ./a.out

Received signal SIGCONT
[1]+ Done ./a.out

В другом терминале

kill -SIGCONT 30379

До сих пор мы видели программу, которая обрабатывает сигнал, генерируемый системой. Теперь давайте посмотрим на сигнал, сгенерированный программой (с помощью функции lift () или команды kill). Эта программа генерирует сигнал SIGTSTP (остановка терминала), действие по умолчанию которого — остановить выполнение. Тем не менее, поскольку мы сейчас обрабатываем сигнал вместо действия по умолчанию, он придет к определенному обработчику. В этом случае мы просто печатаем сообщение и выходим.

/* signal_raising_handling.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

void handler_sigtstp(int signum);

int main() {
   void (*sigHandlerReturn)(int);
   sigHandlerReturn = signal(SIGTSTP, handler_sigtstp);
   if (sigHandlerReturn == SIG_ERR) {
      perror("Signal Error: ");
      return 1;
   }
   printf("Testing SIGTSTP\n");
   raise(SIGTSTP);
   return 0;
}

void handler_sigtstp(int signum) {
   if (signum == SIGTSTP) {
      printf("Received SIGTSTP\n");
      exit(0);
   }
   else
      printf("Received %d Signal\n", signum);
      return;
}

Шаги компиляции и выполнения

Testing SIGTSTP
Received SIGTSTP

Мы видели случаи выполнения действий по умолчанию или обработки сигнала. Теперь пришло время игнорировать сигнал. Здесь, в этом примере программы, мы регистрируем сигнал SIGTSTP для игнорирования через SIG_IGN, а затем поднимаем сигнал SIGTSTP (остановка терминала). Когда генерируется сигнал SIGTSTP, это будет игнорироваться.

/* signal_raising_ignoring.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>

void handler_sigtstp(int signum);

int main() {
   void (*sigHandlerReturn)(int);
   sigHandlerReturn = signal(SIGTSTP, SIG_IGN);
   if (sigHandlerReturn == SIG_ERR) {
      perror("Signal Error: ");
      return 1;
   }
   printf("Testing SIGTSTP\n");
   raise(SIGTSTP);
   printf("Signal SIGTSTP is ignored\n");
   return 0;
}

Шаги компиляции и выполнения

Testing SIGTSTP
Signal SIGTSTP is ignored

До сих пор мы наблюдали, что у нас есть один обработчик сигнала для обработки одного сигнала. Можем ли мы иметь один обработчик для обработки нескольких сигналов? Ответ — да. Давайте рассмотрим это с помощью программы.

Следующая программа делает следующее —

Шаг 1 — Регистрирует обработчик (handleSignals) для перехвата или обработки сигналов SIGINT (CTRL + C) или SIGQUIT (CTRL + \)

Шаг 2 — Если пользователь генерирует сигнал SIGQUIT (либо с помощью команды kill, либо с помощью клавиатуры с помощью CTRL + \), обработчик просто печатает сообщение как возврат.

Шаг 3 — Если пользователь генерирует сигнал SIGINT (либо с помощью команды kill, либо с помощью клавиатуры с помощью CTRL + C) в первый раз, он изменяет сигнал для выполнения действия по умолчанию (с SIG_DFL) со следующего раза.

Шаг 4 — Если пользователь генерирует сигнал SIGINT второй раз, он выполняет действие по умолчанию, которое является завершением программы.

/* Filename: sigHandler.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>

void handleSignals(int signum);

int main(void) {
   void (*sigHandlerInterrupt)(int);
   void (*sigHandlerQuit)(int);
   void (*sigHandlerReturn)(int);
   sigHandlerInterrupt = sigHandlerQuit = handleSignals;
   sigHandlerReturn = signal(SIGINT, sigHandlerInterrupt);
   if (sigHandlerReturn == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   sigHandlerReturn = signal(SIGQUIT, sigHandlerQuit);
   
   if (sigHandlerReturn == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   while (1) {
      printf("\nTo terminate this program, perform the following: \n");
      printf("1. Open another terminal\n");
      printf("2. Issue command: kill %d or issue CTRL+C 2 times (second time it terminates)\n", getpid());
      sleep(10);
   }
   return 0;
}

void handleSignals(int signum) {
   switch(signum) {
      case SIGINT:
      printf("\nYou pressed CTRL+C \n");
      printf("Now reverting SIGINT signal to default action\n");
      signal(SIGINT, SIG_DFL);
      break;
      case SIGQUIT:
      printf("\nYou pressed CTRL+\\ \n");
      break;
      default:
      printf("\nReceived signal number %d\n", signum);
      break;
   }
   return;
}

Шаги компиляции и выполнения

To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
          
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^\You pressed CTRL+\
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 120
Terminated

Еще один терминал

kill 71

Второй метод

To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action

To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C

Мы знаем, что для обработки сигнала у нас есть два системных вызова, т. Е. Signal () или sigaction (). До сих пор мы видели системный вызов signal (), теперь пришло время для системного вызова sigaction (). Давайте изменим вышеуказанную программу, чтобы она выполнялась с использованием sigaction () следующим образом:

/* Filename: sigHandlerSigAction.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>

void handleSignals(int signum);

int main(void) {
   void (*sigHandlerReturn)(int);
   struct sigaction mysigaction;
   mysigaction.sa_handler = handleSignals;
   sigemptyset(&mysigaction.sa_mask);
   mysigaction.sa_flags = 0;
   sigaction(SIGINT, &mysigaction, NULL);
   
   if (mysigaction.sa_handler == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   mysigaction.sa_handler = handleSignals;
   sigemptyset(&mysigaction.sa_mask);
   mysigaction.sa_flags = 0;
   sigaction(SIGQUIT, &mysigaction, NULL);
   
   if (mysigaction.sa_handler == SIG_ERR) {
      perror("signal error: ");
      return 1;
   }
   while (-1) {
      printf("\nTo terminate this program, perform either of the following: \n");
      printf("1. Open another terminal and issue command: kill %d\n", getpid());
      printf("2. Issue CTRL+C 2 times (second time it terminates)\n");
      sleep(10);
   }
   return 0;
}

void handleSignals(int signum) {
   switch(signum) {
      case SIGINT:
      printf("\nYou have entered CTRL+C \n");
      printf("Now reverting SIGINT signal to perform default action\n");
      signal(SIGINT, SIG_DFL);
      break;
      case SIGQUIT:
      printf("\nYou have entered CTRL+\\ \n");
      break;
      default:
      printf("\nReceived signal number %d\n", signum);
      break;
   }
   return;
}

Давайте посмотрим процесс компиляции и выполнения. В процессе выполнения, давайте посмотрим дважды на CTRL + C, оставшиеся проверки / способы (как указано выше) можно попробовать и для этой программы.