Behandeln Sie das SIGINT-Signal in C
-
Verwenden Sie die Funktion
signal
, um die Signalhandler-RoutineSIGINT
zu registrieren -
Verwenden Sie die Funktion
sigaction
, um die Signalhandler-RoutineSIGINT
zu registrieren
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);
}
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