Gérer le signal SIGABRT en C++

  1. Utilisez la sigaction pour enregistrer le gestionnaire de signal SIGABRT
  2. Utilisez la variable sig_atomic_t dans le gestionnaire de signal

Cet article présente plusieurs méthodes de traitement du signal SIGABRT en C++.

Utilisez la sigaction pour enregistrer le gestionnaire de signal SIGABRT

Les systèmes d’exploitation basés sur Unix supportent la fonctionnalité appelée signal, un mécanisme par lequel un programme peut envoyer un message de manière asynchrone à un autre programme. Les signaux sont généralement envoyés par l’utilisateur ou par les processus du système d’exploitation qui doivent interrompre le programme. Le gestionnaire de signal est la section de code qui est appelée si le programme reçoit le signal donné. Il existe des signaux standard pour des tâches telles que l’interruption du programme, l’arrêt du programme et la poursuite du programme avec les gestionnaires de signaux par défaut correspondants. Cependant, l’utilisateur peut remplacer la plupart de ces gestionnaires de signaux par des fonctions personnalisées, et la sigaction est la fonction qui l’enregistre.

Notez que SIGABRT est l’un des signaux qui entraîne l’arrêt du programme par défaut. Dans l’exemple de code suivant, nous surchargeons son gestionnaire de signal et assignons notre fonction définie (cleanupRoutine) à être appelée une fois que le signal est reçu.

Dans un premier temps, l’objet de type struct sigaction doit être déclaré et initialisé avec l’appel de fonction memset.

Ensuite, son membre de données sa_handler doit recevoir l’adresse de la fonction qui doit être appelée.

Après cela, nous devons empêcher les autres signaux d’interrompre le gestionnaire SIGABRT, et l’appel de fonction sigfillset y parvient.

Enfin, nous enregistrons le gestionnaire de signal avec l’appel sigaction qui prend trois arguments : le numéro du signal, l’adresse de la struct sigaction, et une structure optionnelle où l’action précédente peut être sauvegardée.

Dans ce cas, nous ignorons le troisième argument, mais le nullptr doit encore être spécifié comme paramètre. Nous avons défini la fonction cleanupRoutine pour imprimer la chaîne dans la console et ensuite quitter le programme pour vérifier facilement l’exécution correcte du gestionnaire. Le reste du programme est la boucle infinie qui doit être interrompue lorsque l’utilisateur envoie le signal SIGABRT. Pour tester le programme, exécutez le dans une fenêtre de terminal et envoyez le signal depuis la seconde fenêtre en exécutant la commande suivante kill -SIGABRT pid_num_of_program.

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

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);
}

Utilisez la variable sig_atomic_t dans le gestionnaire de signal

Les gestionnaires de signaux sont des types de fonctions spéciales qui doivent avoir certaines caractéristiques. En effet, les seules variables dont le fonctionnement correct est garanti sont les variables atomiques. Il existe un type spécial sig_atomic_t, un entier qui peut être défini pendant l’exécution du gestionnaire de signal. Cette variable est déclarée avec le mot-clé volatile, et toute modification de celle-ci est vue globalement. L’exemple suivant montre comment on peut inclure cette variable comme condition à la déclaration de la boucle.

Notez que le gestionnaire ne met la variable de 1 à 0 et ne quitte le programme que si le signal est reçu. Cela signifie que le programme essaie de continuer à partir du point où il a été interrompu. Dans ce cas, l’itération de la boucle est relancée, et lorsque la condition est vérifiée comme étant fausse, elle saute hors de la boucle. De cette façon, l’utilisateur peut contrôler le comportement du programme en utilisant les signaux.

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

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);
}