C Programmation

Comment utiliser les gestionnaires de signaux en langage C?

Comment utiliser les gestionnaires de signaux en langage C?
Dans cet article, nous allons vous montrer comment utiliser les gestionnaires de signaux sous Linux en utilisant le langage C. Mais nous allons d'abord discuter de ce qu'est un signal, comment il va générer des signaux communs que vous pouvez utiliser dans votre programme, puis nous verrons comment divers signaux peuvent être gérés par un programme pendant que le programme s'exécute. Alors, commençons.

Signal

Un signal est un événement qui est généré pour notifier un processus ou un thread qu'une situation importante est arrivée. Lorsqu'un processus ou un thread a reçu un signal, le processus ou le thread arrête ce qu'il fait et prend des mesures. Le signal peut être utile pour la communication inter-processus.

Signaux standards

Les signaux sont définis dans le fichier d'en-tête signal.h en tant que macro-constante. Le nom du signal commence par un « SIG » et est suivi d'une brève description du signal. Ainsi, chaque signal a une valeur numérique unique. Votre programme doit toujours utiliser le nom des signaux, pas le numéro des signaux. La raison en est que le numéro de signal peut différer selon le système, mais la signification des noms sera standard.

La macro NSIG est le nombre total de signal défini. La valeur de NSIG est supérieur de un au nombre total de signaux définis (tous les numéros de signaux sont attribués consécutivement).

Voici les signaux standard :

Nom du signal La description
S'INSCRIRE Raccrocher le processus. Le signal SIGHUP est utilisé pour signaler la déconnexion du terminal de l'utilisateur, éventuellement parce qu'une connexion à distance est perdue ou raccroche.
SIGINT Interrompre le processus. Lorsque l'utilisateur tape le caractère INTR (normalement Ctrl + C), le signal SIGINT est envoyé.
SIGQUIT Quitter le processus. Lorsque l'utilisateur tape le caractère QUIT (normalement Ctrl + \) le signal SIGQUIT est envoyé.
SIGILL Enseignement illégal. Lorsqu'une tentative d'exécution d'instructions indésirables ou privilégiées est effectuée, le signal SIGILL est généré. De plus, SIGILL peut être généré lorsque la pile déborde ou lorsque le système a du mal à exécuter un gestionnaire de signal.
SIGTRAP Piège à traces. Une instruction de point d'arrêt et une autre instruction de trap généreront le signal SIGTRAP. Le débogueur utilise ce signal.
SIGABRT Avorter. Le signal SIGABRT est généré lorsque la fonction abort() est appelée. Ce signal indique une erreur détectée par le programme lui-même et signalée par l'appel de fonction abort().
SIGFPE Exception à virgule flottante. Lorsqu'une erreur arithmétique fatale s'est produite, le signal SIGFPE est généré.
SIGUSR1 et SIGUSR2 Les signaux SIGUSR1 et SIGUSR2 peuvent être utilisés à votre guise. Il est utile d'écrire un gestionnaire de signal pour eux dans le programme qui reçoit le signal pour une communication inter-processus simple.

Action par défaut des signaux

Chaque signal a une action par défaut, l'une des suivantes :

Terme: Le processus se terminera.
Cœur: Le processus se terminera et produira un fichier de vidage de mémoire.
Ign : Le processus ignorera le signal.
Arrêter: Le processus s'arrêtera.
Suite : Le processus continuera après avoir été arrêté.

L'action par défaut peut être modifiée à l'aide de la fonction de gestionnaire. L'action par défaut de certains signaux ne peut pas être modifiée. SIGKILL et SIGABRT l'action par défaut du signal ne peut pas être modifiée ou ignorée.

Traitement des signaux

Si un processus reçoit un signal, le processus a un choix d'action pour ce type de signal. Le processus peut ignorer le signal, peut spécifier une fonction de gestionnaire ou accepter l'action par défaut pour ce type de signal.

Nous pouvons gérer le signal en utilisant signal ou alors signature une fonction. Ici, nous voyons comment le plus simple signal() la fonction est utilisée pour gérer les signaux.

int signal () (int signum, void (*func)(int))

le signal() appellera le fonction fonction si le processus reçoit un signal signe. le signal() renvoie un pointeur vers la fonction fonction en cas de succès ou renvoie une erreur à errno et -1 sinon.

le fonction le pointeur peut avoir trois valeurs :

  1. SIG_DFL: C'est un pointeur vers la fonction par défaut du système SIG_DFL(), déclaré dans h En tête de fichier. Il est utilisé pour prendre l'action par défaut du signal.
  2. SIG_IGN: C'est un pointeur vers la fonction d'ignorer le système SIG_IGN(),déclaré dans h En tête de fichier.
  3. Pointeur de fonction de gestionnaire défini par l'utilisateur: Le type de fonction de gestionnaire défini par l'utilisateur est vide(*)(int), signifie que le type de retour est void et un argument de type int.

Exemple de gestionnaire de signaux de base

#inclure
#inclure
#inclure
void sig_handler(int signum)
//Le type de retour de la fonction de gestionnaire doit être void
printf("\nFonction de gestionnaire interne\n");

int main()
signal(SIGINT,sig_handler); // Enregistrer le gestionnaire de signal
for(int i=1;;i++)    //Boucle infinie
printf("%d : Fonction principale interne\n",i);
sommeil(1); // Retard de 1 seconde

renvoie 0 ;

Dans la capture d'écran de la sortie de Example1.c, nous pouvons voir que dans la fonction principale la boucle infinie s'exécute. Lorsque l'utilisateur a tapé Ctrl+C, l'exécution de la fonction principale s'arrête et la fonction de gestion du signal est invoquée. Une fois la fonction de gestionnaire terminée, l'exécution de la fonction principale a repris. Lorsque l'utilisateur tape Ctrl+\, le processus est arrêté.

Exemple d'ignorer les signaux

#inclure
#inclure
#inclure
int main()
signal(SIGINT,SIG_IGN); // Enregistrer le gestionnaire de signal pour ignorer le signal
for(int i=1;;i++)    //boucle infinie
printf("%d : Fonction principale interne\n",i);
sommeil(1); // Retard de 1 seconde

renvoie 0 ;

Ici, la fonction de gestionnaire est de s'inscrire à SIG_IGN() fonction pour ignorer l'action du signal. Ainsi, lorsque l'utilisateur a tapé Ctrl+C,  SIGINT le signal est généré mais l'action est ignorée.

Exemple de gestionnaire de signal de réenregistrement

#inclure
#inclure
#inclure
void sig_handler(int signum)
printf("\nFonction de gestionnaire interne\n");
signal(SIGINT,SIG_DFL); // Réenregistrer le gestionnaire de signal pour l'action par défaut

int main()
signal(SIGINT,sig_handler); // Enregistrer le gestionnaire de signal
for(int i=1;;i++)    //Boucle infinie
printf("%d : Fonction principale interne\n",i);
sommeil(1); // Retard de 1 seconde

renvoie 0 ;

Dans la capture d'écran de la sortie de Example3.c, nous pouvons voir que lorsque l'utilisateur a tapé pour la première fois Ctrl + C, la fonction de gestionnaire a invoqué. Dans la fonction de gestionnaire, le gestionnaire de signal se réenregistre sur SIG_DFL pour l'action par défaut du signal. Lorsque l'utilisateur a tapé Ctrl + C pour la deuxième fois, le processus est terminé, ce qui est l'action par défaut de SIGINT signal.

Envoi de signaux :

Un processus peut également envoyer explicitement des signaux à lui-même ou à un autre processus. Les fonctions raise() et kill() peuvent être utilisées pour envoyer des signaux. Les deux fonctions sont déclarées dans le signal.h fichier d'en-tête.

int augmenter(int signum)

La fonction raise() utilisée pour envoyer le signal signe au processus d'appel (lui-même). Il renvoie zéro en cas de succès et une valeur non nulle en cas d'échec.

int kill(pid_t pid, int signum)

La fonction kill utilisée pour envoyer un signal signe à un processus ou à un groupe de processus spécifié par pid.

Exemple de gestionnaire de signaux SIGUSR1

#inclure
#inclure
void sig_handler(int signum)
printf("Fonction de gestionnaire interne\n");

int main()
signal(SIGUSR1, sig_handler); // Enregistrer le gestionnaire de signal
printf("À l'intérieur de la fonction principale\n");
augmenter(SIGUSR1) ;
printf("À l'intérieur de la fonction principale\n");
renvoie 0 ;

Ici, le processus s'envoie le signal SIGUSR1 à l'aide de la fonction raise().

Programme d'exemple de relance avec Kill

#inclure
#inclure
#inclure
void sig_handler(int signum)
printf("Fonction de gestionnaire interne\n");

int main()
pid_t pid;
signal(SIGUSR1, sig_handler); // Enregistrer le gestionnaire de signal
printf("À l'intérieur de la fonction principale\n");
pid=getpid(); //Process ID de lui-même
kill(pid,SIGUSR1) ; // Envoie SIGUSR1 à lui-même
printf("À l'intérieur de la fonction principale\n");
renvoie 0 ;

Ici, le processus envoie SIGUSR1 signal à lui-même en utilisant tuer() une fonction. getpid() est utilisé pour obtenir l'ID de processus de lui-même.

Dans l'exemple suivant, nous verrons comment les processus parent et enfant communiquent (Inter Process Communication) en utilisant tuer() et fonction de signal.

Communication parent-enfant avec des signaux

#inclure
#inclure
#inclure
#inclure
void sig_handler_parent(int signum)
printf("Parent : a reçu un signal de réponse de l'enfant \n");

void sig_handler_child(int signum)
printf("Enfant : a reçu un signal du parent \n");
sommeil(1);
kill(getppid(),SIGUSR1) ;

int main()
pid_t pid;
if((pid=fork())<0)
printf("Fork a échoué\n");
sortie(1);

/* Processus enfant */
sinon si(pid==0)
signal(SIGUSR1, sig_handler_child); // Enregistrer le gestionnaire de signal
printf("Enfant : en attente de signal\n");
pause();

/* Processus parent */
autre
signal(SIGUSR1, sig_handler_parent); // Enregistrer le gestionnaire de signal
sommeil(1);
printf("Parent : envoi du signal à l'enfant\n");
kill(pid,SIGUSR1) ;
printf("Parent : en attente de réponse\n");
pause();

renvoie 0 ;

Ici, fourchette() la fonction crée un processus enfant et renvoie zéro au processus enfant et l'ID du processus enfant au processus parent. Donc, pid a été vérifié pour décider du processus parent et enfant. Dans le processus parent, il est mis en veille pendant 1 seconde afin que le processus enfant puisse enregistrer la fonction de gestionnaire de signal et attendre le signal du parent. Après 1 seconde processus parent envoyer SIGUSR1 signal au processus enfant et attend le signal de réponse de l'enfant. Dans le processus enfant, il attend d'abord le signal du parent et lorsque le signal est reçu, la fonction de gestionnaire est invoquée. À partir de la fonction de gestionnaire, le processus fils envoie un autre SIGUSR1 signal aux parents. Ici getppid() la fonction est utilisée pour obtenir l'ID du processus parent.

Conclusion

Le signal sous Linux est un grand sujet. Dans cet article, nous avons vu comment gérer le signal de manière très basique, et également apprendre comment le signal est généré, comment un processus peut envoyer un signal à lui-même et à d'autres processus, comment le signal peut être utilisé pour la communication inter-processus.

Comment utiliser Xdotool pour stimuler les clics de souris et les frappes sous Linux
Xdotool est un outil de ligne de commande gratuit et open source pour simuler les clics de souris et les frappes. Cet article couvrira un bref guide s...
Top 5 des produits de souris d'ordinateur ergonomiques pour Linux
L'utilisation prolongée de l'ordinateur provoque-t-elle des douleurs au poignet ou aux doigts? Vous souffrez de raideurs articulaires et devez constam...
Comment modifier les paramètres de la souris et du pavé tactile à l'aide de Xinput sous Linux
La plupart des distributions Linux sont livrées avec la bibliothèque "libinput" par défaut pour gérer les événements d'entrée sur un système. Il peut ...