Lidar com o sinal SIGINT em C

Jinku Hu 12 outubro 2023
  1. Use a função sinal para registrar a rotina do manipulador de sinais SIGINT
  2. Use a função sigaction para registrar a rotina do manipulador de sinais SIGINT
Lidar com o sinal SIGINT em C

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);
}
Autor: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

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

Artigo relacionado - C Signal