Lidar com o sinal SIGINT em C
-
Use a função
sinal
para registrar a rotina do manipulador de sinaisSIGINT
-
Use a função
sigaction
para registrar a rotina do manipulador de sinaisSIGINT
Este artigo irá demonstrar vários métodos sobre como lidar com o sinal SIGINT
em C.
Use a função sinal
para registrar a rotina do manipulador de sinais SIGINT
SIGINT
é um dos sinais predefinidos que está associado ao caractere de interrupção do terminal (comumente Ctrl+C). Isso faz com que o shell pare o processo atual e retorne ao seu loop principal, exibindo um novo prompt de comando para o usuário. Observe que os sinais são apenas pequenas notificações enviadas entre os processos dentro do kernel e o espaço do usuário. Às vezes, são chamadas de interrupções de software, pois geralmente param a execução normal do programa e executam a ação especial para o tipo de sinal fornecido. As ações são definidas principalmente como padrão nos sistemas, mas o usuário pode implementar uma função especial e registrá-la como uma nova ação para o sinal.
Lembre-se, porém, de que alguns sinais têm comportamento estritamente fixo atribuído do sistema operacional e não podem ser substituídos, pois o kernel os usa para fazer coisas críticas como encerrar processos que não respondem.
SIGINT
, entretanto, é o tipo de sinal que pode ser manipulado, o que significa que o usuário pode registrar uma função personalizada a ser executada quando o processo receber o sinal. A ação padrão do sinal SIGINT
é para o processo terminar. No código de exemplo a seguir, implementamos um programa que executa um loop infinito while
, chamando a função fprintf
continuamente dentro dele. Embora, logo antes do loop ser iniciado, chamamos a função signal
para registrar a função SIGINT
sigintHandler
como um manipulador, que tem apenas uma chamada de função, imprimindo uma string no stdout
.
Observe que write
é usado em vez de printf
porque o código do manipulador de sinal não deve chamar funções não reentrantes que modificam os dados globais do programa por baixo do capô. Para demonstrar o exemplo, você deve executar o programa e então enviar o sinal SIGINT
do outro terminal para observar o comportamento. Normalmente, ele deve parar de executar o loop while
, imprimir a string "Caught SIGINT!"
E sair com o código de status de sucesso.
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define errExit(msg) \
do { \
perror(msg); \
exit(EXIT_FAILURE); \
} while (0)
static void sigintHandler(int sig) {
write(STDERR_FILENO, "Caught SIGINT!\n", 15);
}
int main(int argc, char *argv[]) {
if (signal(SIGINT, sigintHandler) == SIG_ERR) errExit("signal SIGINT");
while (1) {
fprintf(stderr, "%d", 0);
sleep(3);
}
exit(EXIT_SUCCESS);
}
Use a função sigaction
para registrar a rotina do manipulador de sinais SIGINT
Embora as implementações modernas da chamada de função sinal
em sistemas UNIX funcionem de forma confiável para casos de uso simples, é recomendado utilizar a função sigaction
para registrar os manipuladores de sinal. Ele oferece muito mais opções em comparação com a chamada de signal
, mas também fornece recursos básicos que são necessários para qualquer caso de uso sério dos sinais. sigaction
leva argumentos especiais struct sigaction
para especificar o ponteiro da função do manipulador e outros indicadores com ele. Nesse caso, implementamos um cenário em que um processo filho executa um loop while
com uma variável global shutdown_flag
como uma expressão de condição enquanto o pai espera por ela. Observe que, a variável shutdown_flag
é do tipo sig_atomic_t
, um inteiro especial que pode ser modificado com segurança globalmente a partir do código do manipulador de sinal. Assim, uma vez que o usuário envia o sinal SIGINT
para o processo filho, a função cleanupRoutine
é invocada que define o valor shutdown_flag
para 0
e o controle retorna para o loop while
onde a expressão da condição é avaliada novamente e o zero o força a sair do loop. O filho sai e o pai obtém seu status conforme a função waitpid
retorna.
#define _POSIX_C_SOURCE 199309
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#define errExit(msg) \
do { \
perror(msg); \
exit(EXIT_FAILURE); \
} while (0)
volatile sig_atomic_t shutdown_flag = 1;
void cleanupRoutine(int signal_number) { shutdown_flag = 0; }
int main(void) {
int wstatus;
pid_t c_pid = fork();
if (c_pid == -1) errExit("fork");
if (c_pid == 0) {
printf("printed from child process - %d\n", getpid());
int count = 0;
struct sigaction sigterm_action;
memset(&sigterm_action, 0, sizeof(sigterm_action));
sigterm_action.sa_handler = &cleanupRoutine;
sigterm_action.sa_flags = 0;
// Mask other signals from interrupting SIGINT handler
if (sigfillset(&sigterm_action.sa_mask) != 0) errExit("sigfillset");
// Register SIGINT handler
if (sigaction(SIGINT, &sigterm_action, NULL) != 0) errExit("sigaction");
while (shutdown_flag) {
getpid();
}
printf("pid: %d exited\n", getpid());
exit(EXIT_SUCCESS);
} else {
printf("printed from parent process - %d\n", getpid());
int ret;
if (waitpid(c_pid, &wstatus, WUNTRACED) == -1) errExit("waitpid");
}
exit(EXIT_SUCCESS);
}
Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.
LinkedIn Facebook