Manejar la señal de SIGABRT en C++

Jinku Hu 12 octubre 2023
  1. Usa la sigaction para registrar el manejador de la señal de SIGABRT
  2. Usa la variable sig_atomic_t en el manejador de señales
Manejar la señal de SIGABRT en C++

Este artículo demostrará múltiples métodos de cómo manejar la señal del SIGABRT en C++.

Usa la sigaction para registrar el manejador de la señal de SIGABRT

Los sistemas operativos basados en Unix soportan la característica llamada signal, un mecanismo por el cual un programa puede enviar asincrónicamente un mensaje a otro programa. Las señales se envían normalmente desde el usuario o los procesos del sistema operativo que necesitan interrumpir el programa. El manejador de señales es la sección del código que se llama si el programa recibe la señal dada. Existen algunas señales estándar para tareas como abortar el programa, detener el programa y continuar el programa con sus correspondientes manejadores de señales predeterminados. Sin embargo, el usuario puede anular la mayoría de estos manejadores de señales con funciones definidas por el usuario, y la sigestión es la función que la registra.

Observe que SIGABRT es una de las señales que resulta en la terminación del programa por defecto. En el siguiente ejemplo de código, anulamos su manejador de señales y asignamos nuestra función definida (cleanupRoutine) para que sea llamada una vez que se reciba la señal.

Al principio, el objeto de tipo struct sigaction debe ser declarado e inicializado con la llamada a la función memset.

Luego, a su miembro de datos sa_handler debe asignársele la dirección de la función que necesita ser llamada.

Después de esto, necesitamos enmascarar otras señales para no interrumpir el manejador SIGABRT, y la llamada a la función sigfillset lo consigue.

Finalmente, registramos el manejador de señales con la llamada a sigaction que toma tres argumentos: número de la señal, dirección de la struct sigaction, y una estructura opcional donde la acción anterior puede ser guardada.

En este caso, ignoramos el tercer argumento, pero el nullptr todavía tiene que ser especificado como un parámetro. Definimos la función cleanupRoutine para imprimir la cadena a la consola y luego salir del programa para verificar fácilmente la correcta ejecución del handler. El resto del programa es el bucle infinito que debe ser interrumpido cuando el usuario envía la señal SIGABRT. Para probar el programa, ejecútelo en una ventana terminal y envíe la señal desde la segunda ventana ejecutando el siguiente 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);
}

Usa la variable sig_atomic_t en el manejador de señales

Los manipuladores de señales son tipos especiales de funciones que se requieren para tener ciertas características. A saber, las únicas variables que se garantiza que funcionen correctamente son las variables atómicas. Existe un tipo especial sig_atomic_t, un entero que puede ser establecido durante la ejecución del manejador de señales. Esta variable se declara con la palabra clave volatile, y cualquier modificación a la misma se ve globalmente. El siguiente ejemplo demuestra cómo podemos incluir esta variable como condición de la sentencia loop.

Observa que el manejador sólo establece la variable de 1 a 0 y no sale del programa si se recibe la señal. Lo que significa que el programa intenta continuar desde el punto en el que se interrumpió. En este caso, la iteración del bucle se reinicia, y cuando una vez que se comprueba que la condición es falsa, salta del bucle. De esta manera, el usuario puede controlar el comportamiento del programa usando las señales.

#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