Behandeln Sie das SIGINT-Signal in C

Jinku Hu 12 Oktober 2023
  1. Verwenden Sie die Funktion signal, um die Signalhandler-Routine SIGINT zu registrieren
  2. Verwenden Sie die Funktion sigaction, um die Signalhandler-Routine SIGINT zu registrieren
Behandeln Sie das SIGINT-Signal in C

Dieser Artikel zeigt verschiedene Methoden zum Umgang mit dem Signal SIGINT in C.

Verwenden Sie die Funktion signal, um die Signalhandler-Routine SIGINT zu registrieren

SIGINT ist eines der vordefinierten Signale, die dem Terminal-Interrupt-Zeichen zugeordnet sind (üblicherweise Ctrl + C). Dadurch stoppt die Shell den aktuellen Prozess und kehrt zu ihrer Hauptschleife zurück, wobei dem Benutzer eine neue Eingabeaufforderung angezeigt wird. Beachten Sie, dass Signale nur kleine Benachrichtigungen sind, die zwischen Prozessen im Kernel und im Benutzerbereich gesendet werden. Sie werden manchmal als Software-Interrupts bezeichnet, da sie normalerweise die normale Ausführung des Programms stoppen und die Sonderaktion für den angegebenen Signaltyp ausführen. Die Aktionen werden meist systemübergreifend als Standard definiert, der Benutzer kann jedoch eine spezielle Funktion implementieren und als neue Aktion für das Signal registrieren.

Beachten Sie jedoch, dass einige Signale ein vom Betriebssystem streng festgelegtes Verhalten aufweisen und nicht überschrieben werden können, da der Kernel sie verwendet, um kritische Dinge wie das Beenden nicht reagierender Prozesse auszuführen.

SIGINT ist jedoch die Art von Signal, die verarbeitet werden kann, was bedeutet, dass der Benutzer eine benutzerdefinierte Funktion registrieren kann, die ausgeführt werden soll, wenn der Prozess das Signal empfängt. Die Standardaktion des SIGINT-Signals ist das Beenden des Prozesses. Im folgenden Beispielcode implementieren wir ein Programm, das eine Endlosschleife while ausführt und dabei die Funktion fprintf kontinuierlich aufruft. Obwohl wir kurz vor dem Start der Schleife die Funktion signal aufrufen, um die Funktion SIGINT sigintHandler als Handler zu registrieren, der nur einen Funktionsaufruf hat und eine Zeichenkette an das stdout druckt.

Beachten Sie, dass anstelle von printf write verwendet wird, da der Signalhandler-Code keine nicht wiedereintretenden Funktionen aufrufen darf, die die globalen Programmdaten unter der Haube ändern. Um das Beispiel zu demonstrieren, sollten Sie das Programm ausführen und dann das Signal SIGINT vom anderen Terminal senden, um das Verhalten zu beobachten. Normalerweise sollte die Ausführung der while-Schleife beendet, die Zeichenkette "Caught SIGINT!" Gedruckt und mit dem Erfolgsstatuscode beendet werden.

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

Verwenden Sie die Funktion sigaction, um die Signalhandler-Routine SIGINT zu registrieren

Obwohl moderne Implementierungen des Funktionsaufrufs signal in UNIX-Systemen für einfache Anwendungsfälle zuverlässig funktionieren, wird empfohlen, die Funktion sigaction zur Registrierung der Signalhandler zu verwenden. Es bietet wesentlich mehr Optionen als der signal-Anruf, bietet aber auch Kernfunktionen, die für jeden ernsthaften Anwendungsfall der Signale erforderlich sind. sigaction verwendet spezielle struct sigaction-Argumente, um den Handlerfunktionszeiger und andere Indikatoren damit anzugeben. In diesem Fall implementieren wir ein Szenario, in dem ein untergeordneter Prozess eine while-Schleife mit einer globalen Variablen shutdown_flag als Bedingungsausdruck ausführt, während der übergeordnete Prozess darauf wartet. Beachten Sie, dass die Variable shutdown_flag vom Typ sig_atomic_t ist, eine spezielle Ganzzahl, die sicher global aus dem Signalhandler-Code geändert werden kann. Sobald der Benutzer das Signal SIGINT an den untergeordneten Prozess sendet, wird die Funktion cleanupRoutine aufgerufen, die den Wert shutdown_flag auf den Wert 0 setzt, und das Steuerelement kehrt zur Schleife while zurück, in der der Bedingungsausdruck erneut und ausgewertet wird Die Null zwingt es, aus der Schleife auszubrechen. Das Kind wird beendet und das Elternteil erhält seinen Status, wenn die Funktion waitpid zurückkehrt.

#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

Verwandter Artikel - C Signal