Межпроцессное взаимодействие - сигналы
А signal- это уведомление для процесса, указывающее на наступление события. Сигнал еще называютsoftware interrupt и непредсказуемо узнать его возникновение, поэтому его также называют asynchronous event.
Сигнал может быть указан с помощью номера или имени, обычно имена сигналов начинаются с SIG. Доступные сигналы можно проверить с помощью команды kill –l (l для перечисления имен сигналов), которая выглядит следующим образом:
Каждый раз, когда возникает сигнал (программный или сгенерированный системой), выполняется действие по умолчанию. Что делать, если вы не хотите выполнять действие по умолчанию, но хотите выполнить свои собственные действия при получении сигнала? Возможно ли это для всех сигналов? Да, можно обработать сигнал, но не для всех сигналов. Что, если вы хотите игнорировать сигналы, возможно ли это? Да, сигнал можно игнорировать. Игнорирование сигнала не означает ни выполнения действия по умолчанию, ни обработки сигнала. Почти все сигналы можно игнорировать или обрабатывать. Сигналы, которые нельзя игнорировать или обработать / поймать, - это SIGSTOP и SIGKILL.
Таким образом, действия, выполняемые для сигналов, следующие:
- Действие по умолчанию
- Обработка сигнала
- Игнорировать сигнал
Как уже говорилось, сигнал можно обработать, изменив выполнение действия по умолчанию. Обработка сигналов может выполняться одним из двух способов, то есть с помощью системных вызовов signal () и sigaction ().
#include <signal.h>
typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);
Системный вызов signal () будет вызывать зарегистрированный обработчик после генерации сигнала, как указано в signum. Обработчик может быть либо одним из SIG_IGN (игнорирование сигнала), SIG_DFL (возврат сигнала к механизму по умолчанию) или определяемым пользователем обработчиком сигнала или адресом функции.
Этот системный вызов в случае успеха возвращает адрес функции, которая принимает целочисленный аргумент и не имеет возвращаемого значения. Этот вызов возвращает SIG_ERR в случае ошибки.
Хотя с помощью signal () можно вызвать соответствующий обработчик сигналов, зарегистрированный пользователем, точная настройка, такая как маскирование сигналов, которые должны быть заблокированы, изменение поведения сигнала и другие функции невозможны. Это возможно с помощью системного вызова sigaction ().
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
Этот системный вызов используется для проверки или изменения действия сигнала. Если акт не является нулевым, новое действие для signal signum устанавливается из акта. Если oldact не равен нулю, предыдущее действие сохраняется в oldact.
Структура sigaction содержит следующие поля -
Field 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 содержит информацию о сигнале, такую как номер сигнала, который должен быть доставлен, значение сигнала, идентификатор процесса, реальный идентификатор пользователя процесса отправки и т. Д.
Field 2 - Набор сигналов, которые нужно заблокировать.
int sa_mask;
Эта переменная определяет маску сигналов, которые должны быть заблокированы во время выполнения обработчика сигналов.
Field 3 - Специальные флаги.
int sa_flags;
В этом поле указывается набор флагов, которые изменяют поведение сигнала.
Field 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
Как обсуждалось, сигналы генерируются системой (при выполнении определенных операций, таких как деление на ноль и т. Д.), Или пользователь также может генерировать сигнал программно. Если вы хотите сгенерировать сигнал программно, используйте библиотечную функцию raise ().
Теперь давайте возьмем другую программу, чтобы продемонстрировать обработку и игнорирование сигнала.
Предположим, что мы подняли сигнал с помощью функции raise (), что происходит дальше? После подачи сигнала выполнение текущего процесса останавливается. Что тогда происходит с остановленным процессом? Может быть два сценария: во-первых, продолжить выполнение, когда это необходимо. Во-вторых, завершите (с помощью команды kill) процесс.
Чтобы продолжить выполнение остановленного процесса, отправьте SIGCONT этому конкретному процессу. Вы также можете выполнить команды fg (передний план) или bg (фон), чтобы продолжить выполнение. Здесь команды перезапустили бы выполнение только последнего процесса. Если остановлено более одного процесса, возобновляется только последний процесс. Если вы хотите возобновить ранее остановленный процесс, возобновите задания (используя fg / bg) вместе с номером задания.
Следующая программа используется для поднятия сигнала SIGSTOP с помощью функции raise (). Сигнал 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
До сих пор мы видели программу, которая обрабатывает сигнал, генерируемый системой. Теперь давайте посмотрим, какой сигнал генерируется программой (с помощью функции raise () или команды 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
До сих пор мы наблюдали, что у нас есть один обработчик сигнала для обработки одного сигнала. Можем ли мы иметь один обработчик для обработки нескольких сигналов? Ответ - да. Давайте рассмотрим это с помощью программы.
Следующая программа делает следующее -
Step 1 - Регистрирует обработчик (handleSignals) для перехвата или обработки сигналов SIGINT (CTRL + C) или SIGQUIT (CTRL + \)
Step 2 - Если пользователь генерирует сигнал SIGQUIT (либо с помощью команды kill, либо с клавиатуры с помощью CTRL + \), обработчик просто печатает сообщение как return.
Step 3 - Если пользователь генерирует сигнал SIGINT (либо с помощью команды kill, либо с клавиатуры с помощью CTRL + C) в первый раз, то он изменяет сигнал для выполнения действия по умолчанию (с SIG_DFL) в следующий раз.
Step 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, оставшиеся проверки / способы (как указано выше) вы также можете попробовать для этой программы.
Этапы компиляции и выполнения
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C
You have entered CTRL+C
Now reverting SIGINT signal to perform default action
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C