Comment utiliser les gestionnaires de signaux en langage C ?

How Use Signal Handlers C Language



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 traité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 Raccrochez 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 Quittez le processus. Lorsque l'utilisateur tape le caractère QUIT (normalement Ctrl + ), le signal SIGQUIT est envoyé.
JOINT Enseignement illégal. Lorsqu'une tentative est faite pour exécuter des instructions indésirables ou privilégiées, 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 des difficultés à exécuter un gestionnaire de signal.
SIGTRAP Piège à traces. Une instruction de point d'arrêt et d'autres instructions de déroutement 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 se produit, 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 simple communication inter-processus.

Action par défaut des signaux

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

Terme: Le processus se terminera.
Coeur: 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.
Compte: Le processus continuera à partir de l'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 le choix d'une 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.

  • Si l'action spécifiée pour le signal est ignorée, le signal est supprimé immédiatement.
  • Le programme peut enregistrer une fonction de gestionnaire en utilisant une fonction telle que signal ou signature . C'est ce qu'on appelle un gestionnaire capte le signal.
  • Si le signal n'a été ni traité ni ignoré, son action par défaut a lieu.

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

entiersignal() (entiersigne, annuler (*fonction)(entier))

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

Les 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é en 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

#comprendre
#comprendre
#comprendre
annulersig_handler(entiersigne){

//Le type de retour de la fonction de gestionnaire doit être void
imprimer (' Fonction de gestionnaire intérieur ');
}

entierprincipale(){
signal(SIGINT,sig_handler); // Enregistrer le gestionnaire de signal
pour(entierje=1;;je++){ //Boucle infinie
imprimer ('%d : Fonction principale interne ',je);
dormir(1); // Retard de 1 seconde
}
revenir 0;
}

Dans la capture d'écran de la sortie de Example1.c, nous pouvons voir que dans la fonction principale, la boucle infinie est en cours d'exécution. 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

#comprendre
#comprendre
#comprendre
entierprincipale(){
signal(SIGINT,SIG_IGN); // Enregistrer le gestionnaire de signal pour ignorer le signal

pour(entierje=1;;je++){ //Boucle infinie
imprimer ('%d : Fonction principale interne ',je);
dormir(1); // Retard de 1 seconde
}
revenir 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

#comprendre
#comprendre
#comprendre

annulersig_handler(entiersigne){
imprimer (' Fonction de gestionnaire intérieur ');
signal(SIGINT,SIG_DFL); // Réenregistrer le gestionnaire de signal pour l'action par défaut
}

entierprincipale(){
signal(SIGINT,sig_handler); // Enregistrer le gestionnaire de signal
pour(entierje=1;;je++){ //Boucle infinie
imprimer ('%d : Fonction principale interne ',je);
dormir(1); // Retard de 1 seconde
}
revenir 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 été invoquée. Dans la fonction de gestionnaire, le gestionnaire de signal se réenregistre pour 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 fichier d'en-tête signal.h.

entier augmenter (entiersigne)

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

entiertuer(pid_t pid, entiersigne)

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

#comprendre
#comprendre

annulersig_handler(entiersigne){
imprimer ('Fonction de gestionnaire à l'intérieur ');
}

entierprincipale(){
signal(SIGUSR1,sig_handler); // Enregistrer le gestionnaire de signal
imprimer ('À l'intérieur de la fonction principale ');
augmenter (SIGUSR1);
imprimer ('À l'intérieur de la fonction principale ');
revenir 0;
}

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

Programme d'exemple de relance avec Kill

#comprendre
#comprendre
#comprendre
annulersig_handler(entiersigne){
imprimer ('Fonction de gestionnaire à l'intérieur ');
}

entierprincipale(){
pid_t pid;
signal(SIGUSR1,sig_handler); // Enregistrer le gestionnaire de signal
imprimer ('À l'intérieur de la fonction principale ');
pid=getpid(); //Process ID de lui-même
tuer(pid,SIGUSR1); // Envoie SIGUSR1 à lui-même
imprimer ('À l'intérieur de la fonction principale ');
revenir 0;
}

Ici, le processus envoie SIGUSR1 signal à lui-même en utilisant tuer() fonction. getpid() est utilisé pour obtenir l'ID du processus 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

#comprendre
#comprendre
#comprendre
#comprendre
annulersig_handler_parent(entiersigne){
imprimer ('Parent : A reçu un signal de réponse de l'enfant ');
}

annulersig_handler_child(entiersigne){
imprimer ('Enfant : A reçu un signal du parent ');
dormir(1);
tuer(getppid(),SIGUSR1);
}

entierprincipale(){
pid_t pid;
si((pid=fourchette())<0){
imprimer ('Fork a échoué ');
sortir (1);
}
/* Processus enfant */
autre si(pid==0){
signal(SIGUSR1,sig_handler_child); // Enregistrer le gestionnaire de signal
imprimer ('Enfant : en attente de signal ');
pause();
}
/* Processus parent */
autre{
signal(SIGUSR1,sig_handler_parent); // Enregistrer le gestionnaire de signal
dormir(1);
imprimer ('Parent : envoyer un signal à l'enfant ');
tuer(pid,SIGUSR1);
imprimer ('Parent : en attente de réponse ');
pause();
}
revenir 0;
}

Ici, fourchette() La fonction crée un processus enfant et renvoie zéro au processus enfant et l'ID de processus enfant au processus parent. Ainsi, 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 attendez 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

Signal sous Linux est un sujet important. 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.