Comment utiliser les gestionnaires de signaux en langue C?

Comment utiliser les gestionnaires de signaux en langue C?
Dans cet article, nous allons vous montrer comment utiliser les gestionnaires de signaux dans Linux en utilisant le langage C. Mais nous allons d'abord discuter du signal, comment cela générera des signaux courants que vous pouvez utiliser dans votre programme, puis nous examinerons 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 généré pour informer 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êtera ce qu'il fait et prendra des mesures. Le signal peut être utile pour la communication inter-processus.

Signaux standard

Les signaux sont définis dans le fichier d'en-tête signal.H En tant que constante macro. Le nom du signal a commencé avec un «sig» et 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 de signaux. La raison en est que le numéro de signal peut différer en fonction du système mais la signification des noms sera standard.

La macro Nsig Le nombre total de signal est-il défini. La valeur de Nsig est un plus grand que le nombre total de signal défini (tous les nombres de signaux sont alloués consécutivement).

Voici les signaux standard:

Nom du signal Description
Faire un coup de pouce Accrocher le processus. Le signal SIGHUP est utilisé pour signaler la déconnexion du terminal de l'utilisateur, peut-être parce qu'une connexion distante 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 quitte (normalement Ctrl + \), le signal Sigquit est envoyé.
Sigill Instruction illégale. Lorsqu'une tentative est faite pour exécuter des ordures ou des instructions 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 du mal à exécuter un gestionnaire de signaux.
Sigtrap Trace. Une instruction de point d'arrêt et d'autres instructions de trap générent le signal Sigtrap. Le débogueur utilise ce signal.
Sigabrt Avorter. Le signal Sigabrt est généré lorsque la fonction about () est appelée. Ce signal indique une erreur qui est détectée par le programme lui-même et rapporté par l'appel de fonction Abort ().
Sigfpe Exception à point flottante. Lorsqu'une erreur arithmétique mortelle s'est produite, le signal SigFPE est généré.
Sigusr1 et Sigusr2 Les signaux SIGUSR1 et SIGUSR2 peuvent être utilisés comme vous le souhaitez. Il est utile d'écrire un gestionnaire de signaux pour eux dans le programme qui reçoit le signal pour une communication interprète simple.

Action par défaut des signaux

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

Terme: Le processus se terminera.
Cœur: Le processus se terminera et produira un fichier de vidage de base.
IGN: Le processus ignorera le signal.
Arrêt: Le processus s'arrêtera.
Cont: Le processus continuera d'être arrêté.

L'action par défaut peut être modifiée à l'aide de la fonction du gestionnaire. L'action par défaut d'un signal 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.

Manipulation du signal

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, 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, alors le signal est jeté immédiatement.
  • Le programme peut enregistrer une fonction de gestionnaire à l'aide de la fonction telle que signal ou sigaction. C'est ce qu'on appelle un gestionnaire attrape le signal.
  • Si le signal n'a pas été manipulé ni ignoré, son action par défaut a lieu.

Nous pouvons gérer le signal en utilisant signal ou sigaction 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 func fonction si le processus reçoit un signal signal. Le signal() Renvoie un pointeur pour fonctionner func En cas de succès ou il renvoie une erreur à Errno et -1 sinon.

Le func Le pointeur peut avoir trois valeurs:

  1. Sig_dfl: C'est une fonction de par défaut de pointeur vers le 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 le système ignorer la fonction Sig_ign (),déclaré dans H En tête de fichier.
  3. Pointeur de fonction du gestionnaire défini par l'utilisateur: Le type de fonction de gestionnaire défini par l'utilisateur est void (*) (int), signifie que le type de retour est vide et un argument de type int.

Exemple de gestionnaire de signal de base

#inclure
#inclure
#inclure
void sig_handler (int Signum)
// Le type de retour de la fonction de gestionnaire doit être vide
printf ("\ ninside Handler function \ n");

int main()
Signal (Sigint, Sig_Handler); // Enregistrer le gestionnaire de signaux
pour (int i = 1 ;; i ++) // boucle infinie
printf ("% d: inside principal fonction \ n", i);
sommeil (1); // retard de 1 seconde

retour 0;

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

Ignorer l'exemple des signaux

#inclure
#inclure
#inclure
int main()
Signal (Sigint, SIG_IGN); // Enregistrer le gestionnaire de signaux pour ignorer le signal
pour (int i = 1 ;; i ++) // boucle infinie
printf ("% d: inside principal fonction \ n", i);
sommeil (1); // retard de 1 seconde

retour 0;

Ici, la fonction du gestionnaire est de vous inscrire à Sig_ign () fonction pour ignorer l'action du signal. Donc, lorsque l'utilisateur a tapé Ctrl + C, Sigint Le signal génère mais l'action est ignorée.

Reriger l'exemple du gestionnaire de signaux

#inclure
#inclure
#inclure
void sig_handler (int Signum)
printf ("\ ninside Handler function \ n");
Signal (Sigint, SIG_DFL); // Registre du maintien du signal pour enregistrer l'action par défaut

int main()
Signal (Sigint, Sig_Handler); // Enregistrer le gestionnaire de signaux
pour (int i = 1 ;; i ++) // boucle infinie
printf ("% d: inside principal fonction \ n", i);
sommeil (1); // retard de 1 seconde

retour 0;

Dans la capture d'écran de la sortie de l'exemple3.C, nous pouvons voir que lorsque l'utilisateur a tapé la première fois Ctrl + C, la fonction de gestionnaire a invoqué. Dans la fonction de gestionnaire, le gestionnaire de signaux s'inscrit 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.

Signaux d'envoi:

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

INT RAPING (INT SIGNUM)

La fonction Raisier () utilisée pour l'envoi du signal signal au processus d'appel (lui-même). Il renvoie zéro en cas de succès et une valeur non nulle s'il échoue.

int kill (pid_t pid, int sigm)

La fonction de mise à mort utilisée pour envoyer un signal signal à un groupe de processus ou de processus spécifié par piquer.

Exemple de gestionnaire de signaux SIGUSR1

#inclure
#inclure
void sig_handler (int Signum)
printf ("Inside Handler function \ n");

int main()
Signal (SIGUSR1, SIG_HANDLER); // Enregistrer le gestionnaire de signaux
printf ("Inside Main Function \ n");
augmenter (Sigusr1);
printf ("Inside Main Function \ n");
retour 0;

Ici, le processus envoie un signal SIGUSR1 à lui-même en utilisant la fonction Raisie ().

Programme de tueurs d'exemple de tue

#inclure
#inclure
#inclure
void sig_handler (int Signum)
printf ("Inside Handler function \ n");

int main()
pid_t pid;
Signal (SIGUSR1, SIG_HANDLER); // Enregistrer le gestionnaire de signaux
printf ("Inside Main Function \ n");
pid = getPid (); // Processus ID de lui-même
Kill (PID, Sigusr1); // Envoyez SIGUSR1 à lui-même
printf ("Inside Main Function \ n");
retour 0;

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

Dans l'exemple suivant, nous verrons comment les processus parents et enfants communiquent (communication inter-processus) en utilisant tuer() et fonction du signal.

Communication des enfants à l'enfant avec les 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 de 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 * /
else if (pid == 0)
Signal (SIGUSR1, SIG_HANDLER_CHILD); // Enregistrer le gestionnaire de signaux
printf ("enfant: en attente de signal \ n");
pause();

/ * Processus parent * /
autre
Signal (SIGUSR1, SIG_HANDLER_PARENT); // Enregistrer le gestionnaire de signaux
sommeil (1);
printf ("Parent: Envoi du signal à Child \ n");
Kill (PID, Sigusr1);
printf ("parent: attendant la réponse \ n");
pause();

retour 0;

Ici, fourchette() La fonction crée un processus enfant et renvoie zéro au processus enfant et à un processus 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 dormi pendant 1 seconde afin que le processus d'enfant puisse enregistrer la fonction du gestionnaire de signaux et attendre le signal du parent. Après 1 seconde processus parent Envoyer Sigusr1 Signal au processus de l'enfant et attendre le signal de réponse de l'enfant. Dans le processus de l'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 de l'enfant en envoie un autre Sigusr1 signal au parent. Ici getppid () La fonction est utilisée pour obtenir l'ID de processus parent.

Conclusion

Le signal dans Linux est un grand sujet. Dans cet article, nous avons vu comment gérer le signal du très basique, et aussi comprendre comment le signal génère, comment un processus peut envoyer un signal à lui-même et un autre processus, comment le signal peut être utilisé pour la communication interprète.