Comunicação entre processos - sinais
UMA signalé uma notificação a um processo indicando a ocorrência de um evento. O sinal também é chamadosoftware interrupt e não é previsível saber sua ocorrência, portanto, também é chamado de asynchronous event.
O sinal pode ser especificado com um número ou nome, geralmente os nomes dos sinais começam com SIG. Os sinais disponíveis podem ser verificados com o comando kill –l (l para Listar nomes de sinais), que é o seguinte -
Sempre que um sinal é gerado (seja programaticamente ou sinal gerado pelo sistema), uma ação padrão é executada. E se você não quiser executar a ação padrão, mas desejar executar suas próprias ações ao receber o sinal? Isso é possível para todos os sinais? Sim, é possível controlar o sinal, mas não para todos os sinais. E se você quiser ignorar os sinais, isso é possível? Sim, é possível ignorar o sinal. Ignorar o sinal implica não realizar a ação padrão nem manipular o sinal. É possível ignorar ou controlar quase todos os sinais. Os sinais que não podem ser ignorados ou manipulados / capturados são SIGSTOP e SIGKILL.
Em resumo, as ações realizadas para os sinais são as seguintes -
- Ação padrão
- Lidar com o sinal
- Ignore o sinal
Conforme discutido, o sinal pode ser manipulado alterando a execução da ação padrão. O tratamento do sinal pode ser feito de uma das duas maneiras, ou seja, por meio de chamadas de sistema, signal () e sigaction ().
#include <signal.h>
typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);
O sinal de chamada do sistema () chamaria o manipulador registrado na geração do sinal, conforme mencionado no signum. O manipulador pode ser um dos SIG_IGN (Ignorando o Sinal), SIG_DFL (Configurando o sinal de volta para o mecanismo padrão) ou o manipulador de sinal definido pelo usuário ou endereço de função.
Esta chamada de sistema em caso de sucesso retorna o endereço de uma função que recebe um argumento inteiro e não tem valor de retorno. Esta chamada retorna SIG_ERR em caso de erro.
Embora com signal () o respectivo manipulador de sinal registrado pelo usuário possa ser chamado, ajustes finos, como mascarar os sinais que deveriam ser bloqueados, modificar o comportamento de um sinal e outras funcionalidades não são possíveis. Isso é possível usando a chamada de sistema sigaction ().
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
Esta chamada de sistema é usada para examinar ou alterar uma ação de sinal. Se o ato não for nulo, a nova ação para sinal signum é instalada a partir do ato. Se oldact não for nulo, a ação anterior será salva em oldact.
A estrutura de sigaction contém os seguintes campos -
Field 1 - Handler mencionado em sa_handler ou sa_sigaction.
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
O manipulador para sa_handler especifica a ação a ser executada com base no signum e com SIG_DFL indicando a ação padrão ou SIG_IGN para ignorar o sinal ou ponteiro para uma função de tratamento de sinal.
O manipulador para sa_sigaction especifica o número do sinal como o primeiro argumento, o ponteiro para a estrutura siginfo_t como o segundo argumento e o ponteiro para o contexto do usuário (verifique getcontext () ou setcontext () para obter mais detalhes) como o terceiro argumento.
A estrutura siginfo_t contém informações do sinal, como o número do sinal a ser entregue, valor do sinal, id do processo, id do usuário real do processo de envio, etc.
Field 2 - Conjunto de sinais a serem bloqueados.
int sa_mask;
Esta variável especifica a máscara de sinais que deve ser bloqueada durante a execução do manipulador de sinais.
Field 3 - Bandeiras especiais.
int sa_flags;
Este campo especifica um conjunto de sinalizadores que modificam o comportamento do sinal.
Field 4 - Restaurar manipulador.
void (*sa_restorer) (void);
Esta chamada de sistema retorna 0 em caso de sucesso e -1 em caso de falha.
Vamos considerar alguns programas de amostra.
Primeiro, vamos começar com um programa de amostra, que gera exceção. Neste programa, estamos tentando realizar a operação de divisão por zero, o que faz com que o sistema gere uma exceção.
/* 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;
}
Etapas de compilação e execução
Floating point exception (core dumped)
Assim, quando estamos tentando realizar uma operação aritmética, o sistema gerou uma exceção de ponto flutuante com dump de núcleo, que é a ação padrão do sinal.
Agora, vamos modificar o código para lidar com esse sinal específico usando a chamada do sistema 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;
}
Etapas de compilação e execução
Received SIGFPE, Divide by Zero Exception
Conforme discutido, os sinais são gerados pelo sistema (ao realizar certas operações, como dividir por zero, etc.) ou o usuário também pode gerar o sinal programaticamente. Se você deseja gerar o sinal programaticamente, use a função de biblioteca raise ().
Agora, vamos usar outro programa para demonstrar como lidar e ignorar o sinal.
Suponha que levantamos um sinal usando raise (), o que acontece então? Após levantar o sinal, a execução do processo atual é interrompida. Então o que acontece com o processo interrompido? Pode haver dois cenários - primeiro, continue a execução sempre que necessário. Em segundo lugar, termine (com o comando kill) o processo.
Para continuar a execução do processo interrompido, envie SIGCONT para esse processo específico. Você também pode emitir os comandos fg (foreground) ou bg (background) para continuar a execução. Aqui, os comandos apenas reiniciariam a execução do último processo. Se mais de um processo for interrompido, apenas o último processo será retomado. Se quiser retomar o processo interrompido anteriormente, retome os trabalhos (usando fg / bg) junto com o número do trabalho.
O programa a seguir é usado para aumentar o sinal SIGSTOP usando a função raise (). O sinal SIGSTOP também pode ser gerado pressionando a tecla CTRL + Z (Control + Z) pelo usuário. Após emitir este sinal, o programa irá parar de executar. Envie o sinal (SIGCONT) para continuar a execução.
No exemplo a seguir, estamos retomando o processo interrompido com o comando fg.
/* signal_raising.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
int main() {
printf("Testing SIGSTOP\n");
raise(SIGSTOP);
return 0;
}
Etapas de compilação e execução
Testing SIGSTOP
[1]+ Stopped ./a.out
./a.out
Agora, aprimore o programa anterior para continuar a execução do processo interrompido emitindo SIGCONT de outro terminal.
/* 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;
}
Etapas de compilação e execução
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
Em outro terminal
kill -SIGCONT 30379
Até agora, vimos o programa que lida com o sinal gerado pelo sistema. Agora, vamos ver o sinal gerado através do programa (usando a função raise () ou através do comando kill). Este programa gera o sinal SIGTSTP (parada do terminal), cuja ação padrão é parar a execução. No entanto, como estamos lidando com o sinal agora em vez da ação padrão, ele chegará ao manipulador definido. Nesse caso, estamos apenas imprimindo a mensagem e saindo.
/* 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;
}
Etapas de compilação e execução
Testing SIGTSTP
Received SIGTSTP
Vimos as instâncias de execução de ação padrão ou manipulação do sinal. Agora, é hora de ignorar o sinal. Aqui, neste programa de amostra, estamos registrando o sinal SIGTSTP para ignorar por meio de SIG_IGN e, em seguida, estamos aumentando o sinal SIGTSTP (parada do terminal). Quando o sinal SIGTSTP está sendo gerado, ele será ignorado.
/* 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;
}
Etapas de compilação e execução
Testing SIGTSTP
Signal SIGTSTP is ignored
Até agora, observamos que temos um manipulador de sinal para lidar com um sinal. Podemos ter um único manipulador para lidar com vários sinais? A resposta é sim. Vamos considerar isso com um programa.
O programa a seguir faz o seguinte -
Step 1 - Registra um manipulador (handleSignals) para capturar ou manipular os sinais SIGINT (CTRL + C) ou SIGQUIT (CTRL + \)
Step 2 - Se o usuário gerar o sinal SIGQUIT (por meio do comando kill ou do controle do teclado com CTRL + \), o manipulador simplesmente imprime a mensagem como retorno.
Step 3 - Se o usuário gerar o sinal SIGINT (através do comando kill ou do controle do teclado com CTRL + C) na primeira vez, ele modifica o sinal para realizar a ação padrão (com SIG_DFL) na próxima vez.
Step 4 - Se o usuário gerar o sinal SIGINT pela segunda vez, ele executa uma ação padrão, que é o encerramento do programa.
/* 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;
}
Etapas de compilação e execução
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
Outro Terminal
kill 71
Segundo método
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
Sabemos que, para tratar um sinal, temos duas chamadas de sistema, ou seja, signal () ou sigaction (). Até agora vimos com a chamada de sistema signal (), agora é hora da chamada de sistema sigaction (). Vamos modificar o programa acima para funcionar usando sigaction () da seguinte forma -
/* 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;
}
Vamos ver o processo de compilação e execução. No processo de execução, vamos ver o problema CTRL + C duas vezes, verificações / maneiras restantes (como acima) que você pode tentar para este programa também.
Etapas de compilação e execução
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