Handhabung das SIGABRT-Signal in C++

  1. Verwenden Sie sigaction, um den SIGABRT-Signal-Handler zu registrieren
  2. Verwendung der Variable sig_atomic_t in Signal-Handlern

Dieser Artikel demonstriert mehrere Methoden, wie das Signal SIGABRT in C++ behandelt werden kann.

Verwenden Sie sigaction, um den SIGABRT-Signal-Handler zu registrieren

Unix-basierte Betriebssysteme unterstützen die Funktion namens Signal, einen Mechanismus, mit dem ein Programm asynchron eine Nachricht an ein anderes Programm senden kann. Signale werden in der Regel vom Benutzer oder den Prozessen des Betriebssystems gesendet, die das Programm unterbrechen müssen. Der Signalhandler ist der Codeabschnitt, der aufgerufen wird, wenn das Programm das gegebene Signal empfängt. Es gibt einige Standardsignale für Aufgaben wie das Abbrechen des Programms, das Anhalten des Programms und das Fortsetzen des Programms mit ihren entsprechenden Standardsignalhandlern. Der Benutzer kann jedoch die meisten dieser Signalhandler mit benutzerdefinierten Funktionen außer Kraft setzen, und sigaction ist die Funktion, die dies registriert.

Beachten Sie, dass SIGABRT eines der Signale ist, das standardmäßig zum Programmabbruch führt. Im nächsten Codebeispiel überschreiben wir seinen Signalhandler und weisen unsere definierte Funktion (cleanupRoutine) zu, die aufgerufen wird, sobald das Signal empfangen wird.

Als erstes sollte das Objekt vom Typ struct sigaction deklariert und mit dem Funktionsaufruf memset initialisiert werden.

Als nächstes sollte seinem Datenelement sa_handler die Adresse der Funktion zugewiesen werden, die aufgerufen werden soll.

Danach müssen wir andere Signale davon abhalten, den SIGABRT-Handler zu unterbrechen, und das erreichen wir mit dem Funktionsaufruf sigfillset.

Schließlich registrieren wir den Signalhandler mit dem Aufruf sigaction, der drei Argumente entgegennimmt: die Signalnummer, die Adresse des struct sigaction und eine optionale Struktur, in der die vorherige Aktion gespeichert werden kann.

In diesem Fall ignorieren wir das dritte Argument, aber die nullptr muss trotzdem als Parameter angegeben werden. Wir haben die Funktion cleanupRoutine definiert, um die Zeichenkette auf der Konsole auszugeben und dann das Programm zu beenden, um die korrekte Ausführung des Handlers leicht zu überprüfen. Der Rest des Programms ist die Endlosschleife, die unterbrochen werden muss, wenn der Benutzer das Signal SIGABRT sendet. Um das Programm zu testen, führen Sie es in einem Terminalfenster aus und senden Sie das Signal vom zweiten Fenster aus, indem Sie den folgenden Befehl kill -SIGABRT pid_num_of_program ausführen.

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

Verwendung der Variable sig_atomic_t in Signal-Handlern

Signal-Handler sind spezielle Arten von Funktionen, die bestimmte Eigenschaften haben müssen. Nämlich, dass die einzigen Variablen, auf denen garantiert korrekt operiert wird, atomare Variablen sind. Es gibt einen speziellen Typ sig_atomic_t, eine Ganzzahl, die während der Ausführung des Signalhandlers gesetzt werden kann. Diese Variable wird mit dem Schlüsselwort volatile deklariert, und jede Änderung an ihr wird global gesehen. Das folgende Beispiel zeigt, wie wir diese Variable als Bedingung in die Schleifenanweisung einbeziehen können.

Beachten Sie, dass der Handler die Variable nur von 1 auf 0 setzt und das Programm nicht verlässt, wenn das Signal empfangen wird. Das bedeutet, dass das Programm versucht, an dem Punkt fortzufahren, an dem es unterbrochen wurde. In diesem Fall wird die Iteration der Schleife neu gestartet, und wenn die Bedingung einmal als falsch überprüft wurde, springt es aus der Schleife heraus. Auf diese Weise kann der Anwender das Programmverhalten über die Signale steuern.

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