Manusear o SIGABRT Sinal em C++

Jinku Hu 12 outubro 2023
  1. Utilize sigaction para registar SIGABRT Signal Handler
  2. Utilize a variável sig_atomic_t Variable in Signal Handler
Manusear o SIGABRT Sinal em C++

Este artigo irá demonstrar múltiplos métodos de como lidar com o sinal SIGABRT em C++.

Utilize sigaction para registar SIGABRT Signal Handler

Os sistemas operacionais baseados em Unix suportam a funcionalidade chamada signal, um mecanismo pelo qual um programa pode enviar uma mensagem a outro programa de forma assíncrona. Os sinais são normalmente enviados pelo utilizador ou pelos processos do sistema operativo que necessitam de interromper o programa. O manipulador do sinal é a secção de código que é chamada se o programa receber o sinal dado. Existem alguns sinais padrão para tarefas como abortar o programa, parar o programa, e continuar o programa com os seus correspondentes manipuladores de sinal padrão. No entanto, o utilizador pode anular a maioria destes manipuladores de sinais com funções personalizadas, e sigaction é a função que a regista.

Note-se que SIGABRT é um dos sinais que resulta na terminação do programa por defeito. No exemplo de código seguinte, anulamos o seu manipulador de sinais e atribuímos a nossa função definida (cleanupRoutine) para ser chamada assim que o sinal for recebido.

No início, o objecto do tipo struct sigaction deve ser declarado e inicializado com a chamada de função memset.

Em seguida, ao seu membro de dados sa_handler deve ser atribuído o endereço da função que precisa de ser chamada.

Depois disto, precisamos de mascarar outros sinais de interrupção do manipulador SIGABRT, e a chamada de função sigfillset consegue isso.

Finalmente, registamos o manipulador de sinais com uma chamada de sigaction que leva três argumentos: número do sinal, endereço da struct sigaction, e uma estrutura opcional onde a acção anterior pode ser guardada.

Neste caso, ignoramos o terceiro argumento, mas o nullptr ainda precisa de ser especificado como um parâmetro. Definimos a função cleanupRoutine para imprimir a string para a consola e depois sair do programa para verificar facilmente a execução correcta do manipulador. O resto do programa é o loop infinito que precisa de ser interrompido quando o utilizador envia o sinal SIGABRT. Para testar o programa, execute-o numa janela terminal e envie o sinal da segunda janela executando o seguinte comando kill -SIGABRT pid_num_of_program.

#include <csignal>
#include <cstring>
#include <iostream>

void cleanupRoutine(int signal_number) {
  write(2, "printed from cleanupRoutine\n", 28);
  _exit(EXIT_SUCCESS);
}

int main() {
  struct sigaction sigabrt_action {};
  memset(&sigabrt_action, 0, sizeof(sigabrt_action));
  sigabrt_action.sa_handler = &cleanupRoutine;

  if (sigfillset(&sigabrt_action.sa_mask) != 0) {
    perror("sigfillset");
    exit(EXIT_FAILURE);
  }
  if (sigaction(SIGABRT, &sigabrt_action, nullptr) != 0) {
    perror("sigaction SIGABRT");
    exit(EXIT_FAILURE);
  }

  int i = 0;
  while (true) {
    i += 1;
  }

  exit(EXIT_SUCCESS);
}

Utilize a variável sig_atomic_t Variable in Signal Handler

Os manipuladores de sinais são tipos especiais de funções que são necessários para ter certas características. Nomeadamente, as únicas variáveis com garantia de funcionamento correcto são as variáveis atómicas. Há um tipo especial sig_atomic_t, um inteiro que pode ser definido durante a execução do manipulador de sinais. Esta variável é declarada com a palavra-chave volatile, e qualquer modificação à mesma é vista globalmente. O exemplo seguinte demonstra como podemos incluir esta variável como uma condição para a declaração do laço.

Note-se que o manipulador apenas define a variável de 1 a 0 e não sai do programa se o sinal for recebido. Ou seja, o programa tenta continuar a partir do ponto em que foi interrompido. Neste caso, a iteração do laço é reiniciada, e quando a condição é verificada para ser falsa, salta para fora do laço. Desta forma, o utilizador pode controlar o comportamento do programa utilizando os sinais.

#include <csignal>
#include <cstring>
#include <iostream>

using std::cout;
using std::endl;

volatile sig_atomic_t shutdown_flag = 1;

void cleanupRoutine(int signal_number) { shutdown_flag = 0; }

int main() {
  struct sigaction sigabrt_action {};
  memset(&sigabrt_action, 0, sizeof(sigabrt_action));
  sigabrt_action.sa_handler = &cleanupRoutine;

  if (sigfillset(&sigabrt_action.sa_mask) != 0) {
    perror("sigfillset");
    exit(EXIT_FAILURE);
  }
  if (sigaction(SIGABRT, &sigabrt_action, nullptr) != 0) {
    perror("sigaction SIGABRT");
    exit(EXIT_FAILURE);
  }

  int i = 0;
  while (shutdown_flag) {
    i += 1;
  }
  cout << "Exiting ..." << endl;

  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