Gérer le signal SIGINT en C
-
Utilisez la fonction
signal
pour enregistrer le programme de gestion des signauxSIGINT
-
Utilisez la fonction
sigaction
pour enregistrer la routine de gestion des signauxSIGINT
Cet article présente plusieurs méthodes pour gérer le signal SIGINT
en C.
Utilisez la fonction signal
pour enregistrer le programme de gestion des signaux SIGINT
SIGINT
est l’un des signaux prédéfinis associés au caractère d’interruption du terminal (généralement Ctrl+C). Cela oblige le shell à arrêter le processus en cours et à revenir à sa boucle principale, affichant une nouvelle invite de commande à l’utilisateur. Notez que les signaux ne sont que de petites notifications envoyées entre les processus à l’intérieur du noyau et l’espace utilisateur. Ils sont parfois appelés interruptions logicielles car ils arrêtent généralement l’exécution normale du programme et exécutent l’action spéciale pour le type de signal donné. Les actions sont principalement définies par défaut dans les systèmes, mais l’utilisateur peut implémenter une fonction spéciale et l’enregistrer en tant que nouvelle action pour le signal.
Attention cependant, certains signaux ont un comportement strictement fixe attribué par le système d’exploitation et ne peuvent pas être remplacés, car le noyau les utilise pour faire des choses critiques comme mettre fin aux processus qui ne répondent pas.
SIGINT
, cependant, est le type de signal qui peut être traité, ce qui signifie que l’utilisateur peut enregistrer une fonction personnalisée à exécuter lorsque le processus reçoit le signal. L’action par défaut du signal SIGINT
est la fin du processus. Dans l’exemple de code suivant, nous implémentons un programme qui exécute une boucle infinie while
, appelant la fonction fprintf
en continu à l’intérieur. Bien que, juste avant le démarrage de la boucle, nous appelons la fonction signal
pour enregistrer la fonction SIGINT
sigintHandler
en tant que gestionnaire, qui n’a qu’un seul appel de fonction, imprimant une chaîne dans stdout
.
Notez que write
est utilisé à la place de printf
car le code du gestionnaire de signaux ne doit pas appeler des fonctions non réentrantes qui modifient les données globales du programme sous le capot. Pour illustrer l’exemple, vous devez exécuter le programme puis envoyer le signal SIGINT
de l’autre terminal pour observer le comportement. Habituellement, il devrait arrêter d’exécuter la boucle while
, afficher la chaîne "Caught SIGINT!"
Et quitter avec le code d’état de réussite.
#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);
}
Utilisez la fonction sigaction
pour enregistrer la routine de gestion des signaux SIGINT
Même si les implémentations modernes de l’appel de la fonction signal
dans les systèmes UNIX fonctionnent de manière fiable pour des cas d’utilisation simples, il est recommandé d’utiliser la fonction sigaction
pour enregistrer les gestionnaires de signaux. Il offre beaucoup plus d’options par rapport à l’appel signal
, mais il fournit également des fonctionnalités de base qui sont nécessaires pour tout cas d’utilisation sérieux des signaux. sigaction
prend des arguments spéciaux struct sigaction
pour spécifier le pointeur de la fonction du gestionnaire et d’autres indicateurs avec lui. Dans ce cas, nous implémentons un scénario où un processus fils exécute une boucle while
avec une variable globale shutdown_flag
comme expression de condition pendant que le parent l’attend. Notez que la variable shutdown_flag
est de type sig_atomic_t
, un entier spécial qui peut être modifié globalement en toute sécurité à partir du code du gestionnaire de signaux. Ainsi, une fois que l’utilisateur envoie le signal SIGINT
au processus fils, la fonction cleanupRoutine
est appelée qui met le shutdown_flag
à la valeur 0
et le contrôle retourne à la boucle while
où l’expression de condition est à nouveau évaluée et le zéro l’oblige à sortir de la boucle. L’enfant sort et le parent obtient son statut au retour de la fonction waitpid
.
#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