Communication inter-processus - Signaux
UNE signalest une notification à un processus indiquant l'occurrence d'un événement. Le signal est également appelésoftware interrupt et n'est pas prévisible de connaître son occurrence, d'où il est également appelé un asynchronous event.
Le signal peut être spécifié avec un numéro ou un nom, généralement les noms de signal commencent par SIG. Les signaux disponibles peuvent être vérifiés avec la commande kill –l (l pour lister les noms de signaux), qui se présente comme suit -
Chaque fois qu'un signal est émis (signal généré par programme ou par le système), une action par défaut est effectuée. Que faire si vous ne souhaitez pas effectuer l'action par défaut, mais souhaitez effectuer vos propres actions à la réception du signal? Est-ce possible pour tous les signaux? Oui, il est possible de gérer le signal mais pas pour tous les signaux. Et si vous voulez ignorer les signaux, est-ce possible? Oui, il est possible d'ignorer le signal. Ignorer le signal n'implique ni l'exécution de l'action par défaut ni la gestion du signal. Il est possible d'ignorer ou de gérer presque tous les signaux. Les signaux qui ne peuvent être ni ignorés ni traités / capturés sont SIGSTOP et SIGKILL.
En résumé, les actions effectuées pour les signaux sont les suivantes -
- Action par défaut
- Gérez le signal
- Ignorer le signal
Comme indiqué, le signal peut être traité en modifiant l'exécution de l'action par défaut. Le traitement du signal peut être effectué de l'une ou l'autre des deux manières, c'est-à-dire par des appels système, signal () et sigaction ().
#include <signal.h>
typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);
Le signal d'appel système () appellerait le gestionnaire enregistré lors de la génération du signal comme mentionné dans signum. Le gestionnaire peut être l'un des SIG_IGN (ignorant le signal), SIG_DFL (remise du signal au mécanisme par défaut) ou un gestionnaire de signal défini par l'utilisateur ou une adresse de fonction.
Cet appel système en cas de succès renvoie l'adresse d'une fonction qui prend un argument entier et n'a pas de valeur de retour. Cet appel renvoie SIG_ERR en cas d'erreur.
Bien qu'avec signal (), le gestionnaire de signal respectif enregistré par l'utilisateur puisse être appelé, un réglage fin tel que le masquage des signaux qui doivent être bloqués, la modification du comportement d'un signal et d'autres fonctionnalités ne sont pas possibles. Ceci est possible en utilisant l'appel système sigaction ().
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
Cet appel système est utilisé pour examiner ou modifier une action de signal. Si l'acte n'est pas nul, la nouvelle action pour signal signum est installée à partir de l'acte. Si oldact n'est pas nul, l'action précédente est enregistrée dans oldact.
La structure sigaction contient les champs suivants -
Field 1 - Handler mentionné soit dans sa_handler, soit dans sa_sigaction.
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
Le gestionnaire de sa_handler spécifie l'action à effectuer en fonction du signum et avec SIG_DFL indiquant l'action par défaut ou SIG_IGN pour ignorer le signal ou le pointeur vers une fonction de gestion de signal.
Le gestionnaire de sa_sigaction spécifie le numéro du signal comme premier argument, le pointeur vers la structure siginfo_t comme deuxième argument et le pointeur vers le contexte utilisateur (vérifiez getcontext () ou setcontext () pour plus de détails) comme troisième argument.
La structure siginfo_t contient des informations de signal telles que le numéro du signal à délivrer, la valeur du signal, l'identifiant du processus, l'identifiant réel de l'utilisateur du processus d'envoi, etc.
Field 2 - Ensemble de signaux à bloquer.
int sa_mask;
Cette variable spécifie le masque des signaux qui doivent être bloqués pendant l'exécution du gestionnaire de signaux.
Field 3 - Drapeaux spéciaux.
int sa_flags;
Ce champ spécifie un ensemble d'indicateurs qui modifient le comportement du signal.
Field 4 - Restaurer le gestionnaire.
void (*sa_restorer) (void);
Cet appel système renvoie 0 en cas de succès et -1 en cas d'échec.
Considérons quelques exemples de programmes.
Commençons par un exemple de programme, qui génère une exception. Dans ce programme, nous essayons d'effectuer une opération de division par zéro, ce qui oblige le système à générer une exception.
/* signal_fpe.c */
#include<stdio.h>
int main() {
int result;
int v1, v2;
v1 = 121;
v2 = 0;
result = v1/v2;
printf("Result of Divide by Zero is %d\n", result);
return 0;
}
Étapes de compilation et d'exécution
Floating point exception (core dumped)
Ainsi, lorsque nous essayons d'effectuer une opération arithmétique, le système a généré une exception en virgule flottante avec vidage de mémoire, qui est l'action par défaut du signal.
Maintenant, modifions le code pour gérer ce signal particulier en utilisant l'appel système signal ().
/* signal_fpe_handler.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_dividebyzero(int signum);
int main() {
int result;
int v1, v2;
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGFPE, handler_dividebyzero);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
v1 = 121;
v2 = 0;
result = v1/v2;
printf("Result of Divide by Zero is %d\n", result);
return 0;
}
void handler_dividebyzero(int signum) {
if (signum == SIGFPE) {
printf("Received SIGFPE, Divide by Zero Exception\n");
exit (0);
}
else
printf("Received %d Signal\n", signum);
return;
}
Étapes de compilation et d'exécution
Received SIGFPE, Divide by Zero Exception
Comme discuté, les signaux sont générés par le système (lors de l'exécution de certaines opérations telles que la division par zéro, etc.) ou l'utilisateur peut également générer le signal par programmation. Si vous souhaitez générer un signal par programme, utilisez la fonction de bibliothèque rise ().
Maintenant, prenons un autre programme pour démontrer la manipulation et ignorer le signal.
Supposons que nous ayons soulevé un signal en utilisant rise (), que se passe-t-il alors? Après avoir augmenté le signal, l'exécution du processus en cours est interrompue. Alors qu'arrive-t-il au processus arrêté? Il peut y avoir deux scénarios - Tout d'abord, continuez l'exécution chaque fois que nécessaire. Deuxièmement, terminez (avec la commande kill) le processus.
Pour continuer l'exécution du processus arrêté, envoyez SIGCONT à ce processus particulier. Vous pouvez également émettre des commandes fg (premier plan) ou bg (arrière-plan) pour continuer l'exécution. Ici, les commandes ne redémarreraient que l'exécution du dernier processus. Si plus d'un processus est arrêté, seul le dernier processus est repris. Si vous souhaitez reprendre le processus précédemment arrêté, reprenez les travaux (en utilisant fg / bg) avec le numéro de travail.
Le programme suivant est utilisé pour augmenter le signal SIGSTOP à l'aide de la fonction rise (). Le signal SIGSTOP peut également être généré en appuyant sur la touche CTRL + Z (Control + Z). Après avoir émis ce signal, le programme s'arrêtera de s'exécuter. Envoyez le signal (SIGCONT) pour continuer l'exécution.
Dans l'exemple suivant, nous reprenons le processus arrêté avec la commande fg.
/* signal_raising.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
int main() {
printf("Testing SIGSTOP\n");
raise(SIGSTOP);
return 0;
}
Étapes de compilation et d'exécution
Testing SIGSTOP
[1]+ Stopped ./a.out
./a.out
Maintenant, améliorez le programme précédent pour continuer l'exécution du processus arrêté en émettant SIGCONT à partir d'un autre terminal.
/* signal_stop_continue.c */
#include<stdio.h>
#include<signal.h>
#include <sys/types.h>
#include <unistd.h>
void handler_sigtstp(int signum);
int main() {
pid_t pid;
printf("Testing SIGSTOP\n");
pid = getpid();
printf("Open Another Terminal and issue following command\n");
printf("kill -SIGCONT %d or kill -CONT %d or kill -18 %d\n", pid, pid, pid);
raise(SIGSTOP);
printf("Received signal SIGCONT\n");
return 0;
}
Étapes de compilation et d'exécution
Testing SIGSTOP
Open Another Terminal and issue following command
kill -SIGCONT 30379 or kill -CONT 30379 or kill -18 30379
[1]+ Stopped ./a.out
Received signal SIGCONT
[1]+ Done ./a.out
Dans un autre terminal
kill -SIGCONT 30379
Jusqu'à présent, nous avons vu le programme qui gère le signal généré par le système. Voyons maintenant le signal généré par le programme (en utilisant la fonction rise () ou via la commande kill). Ce programme génère le signal SIGTSTP (arrêt terminal), dont l'action par défaut est d'arrêter l'exécution. Cependant, puisque nous traitons le signal maintenant au lieu de l'action par défaut, il viendra au gestionnaire défini. Dans ce cas, nous imprimons simplement le message et sortons.
/* signal_raising_handling.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_sigtstp(int signum);
int main() {
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGTSTP, handler_sigtstp);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
printf("Testing SIGTSTP\n");
raise(SIGTSTP);
return 0;
}
void handler_sigtstp(int signum) {
if (signum == SIGTSTP) {
printf("Received SIGTSTP\n");
exit(0);
}
else
printf("Received %d Signal\n", signum);
return;
}
Étapes de compilation et d'exécution
Testing SIGTSTP
Received SIGTSTP
Nous avons vu les instances d'exécution d'une action par défaut ou de gestion du signal. Maintenant, il est temps d'ignorer le signal. Ici, dans cet exemple de programme, nous enregistrons le signal SIGTSTP à ignorer via SIG_IGN puis nous élevons le signal SIGTSTP (arrêt terminal). Lorsque le signal SIGTSTP est généré, il est ignoré.
/* signal_raising_ignoring.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_sigtstp(int signum);
int main() {
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGTSTP, SIG_IGN);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
printf("Testing SIGTSTP\n");
raise(SIGTSTP);
printf("Signal SIGTSTP is ignored\n");
return 0;
}
Étapes de compilation et d'exécution
Testing SIGTSTP
Signal SIGTSTP is ignored
Jusqu'à présent, nous avons observé que nous avons un gestionnaire de signaux pour gérer un signal. Pouvons-nous avoir un seul gestionnaire pour gérer plusieurs signaux? La réponse est oui. Considérons cela avec un programme.
Le programme suivant effectue les opérations suivantes -
Step 1 - Enregistre un gestionnaire (handleSignals) pour capturer ou gérer les signaux SIGINT (CTRL + C) ou SIGQUIT (CTRL + \)
Step 2 - Si l'utilisateur génère le signal SIGQUIT (soit via la commande kill ou le contrôle clavier avec CTRL + \), le gestionnaire imprime simplement le message en retour.
Step 3 - Si l'utilisateur génère le signal SIGINT (soit par la commande kill ou par le contrôle du clavier avec CTRL + C) la première fois, alors il modifie le signal pour effectuer l'action par défaut (avec SIG_DFL) à partir de la prochaine fois.
Step 4 - Si l'utilisateur génère le signal SIGINT une deuxième fois, il effectue une action par défaut, qui est la fin du programme.
/* Filename: sigHandler.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handleSignals(int signum);
int main(void) {
void (*sigHandlerInterrupt)(int);
void (*sigHandlerQuit)(int);
void (*sigHandlerReturn)(int);
sigHandlerInterrupt = sigHandlerQuit = handleSignals;
sigHandlerReturn = signal(SIGINT, sigHandlerInterrupt);
if (sigHandlerReturn == SIG_ERR) {
perror("signal error: ");
return 1;
}
sigHandlerReturn = signal(SIGQUIT, sigHandlerQuit);
if (sigHandlerReturn == SIG_ERR) {
perror("signal error: ");
return 1;
}
while (1) {
printf("\nTo terminate this program, perform the following: \n");
printf("1. Open another terminal\n");
printf("2. Issue command: kill %d or issue CTRL+C 2 times (second time it terminates)\n", getpid());
sleep(10);
}
return 0;
}
void handleSignals(int signum) {
switch(signum) {
case SIGINT:
printf("\nYou pressed CTRL+C \n");
printf("Now reverting SIGINT signal to default action\n");
signal(SIGINT, SIG_DFL);
break;
case SIGQUIT:
printf("\nYou pressed CTRL+\\ \n");
break;
default:
printf("\nReceived signal number %d\n", signum);
break;
}
return;
}
Étapes de compilation et d'exécution
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^\You pressed CTRL+\
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 120
Terminated
Un autre terminal
kill 71
Deuxième méthode
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C
Nous savons que pour gérer un signal, nous avons deux appels système, soit signal () ou sigaction (). Jusqu'à présent, nous l'avons vu avec l'appel système signal (), il est maintenant temps pour l'appel système sigaction (). Modifions le programme ci-dessus pour effectuer en utilisant sigaction () comme suit -
/* Filename: sigHandlerSigAction.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handleSignals(int signum);
int main(void) {
void (*sigHandlerReturn)(int);
struct sigaction mysigaction;
mysigaction.sa_handler = handleSignals;
sigemptyset(&mysigaction.sa_mask);
mysigaction.sa_flags = 0;
sigaction(SIGINT, &mysigaction, NULL);
if (mysigaction.sa_handler == SIG_ERR) {
perror("signal error: ");
return 1;
}
mysigaction.sa_handler = handleSignals;
sigemptyset(&mysigaction.sa_mask);
mysigaction.sa_flags = 0;
sigaction(SIGQUIT, &mysigaction, NULL);
if (mysigaction.sa_handler == SIG_ERR) {
perror("signal error: ");
return 1;
}
while (-1) {
printf("\nTo terminate this program, perform either of the following: \n");
printf("1. Open another terminal and issue command: kill %d\n", getpid());
printf("2. Issue CTRL+C 2 times (second time it terminates)\n");
sleep(10);
}
return 0;
}
void handleSignals(int signum) {
switch(signum) {
case SIGINT:
printf("\nYou have entered CTRL+C \n");
printf("Now reverting SIGINT signal to perform default action\n");
signal(SIGINT, SIG_DFL);
break;
case SIGQUIT:
printf("\nYou have entered CTRL+\\ \n");
break;
default:
printf("\nReceived signal number %d\n", signum);
break;
}
return;
}
Voyons le processus de compilation et d'exécution. Dans le processus d'exécution, voyons le problème CTRL + C deux fois, les vérifications / méthodes restantes (comme ci-dessus) que vous pouvez également essayer pour ce programme.
Étapes de compilation et d'exécution
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C
You have entered CTRL+C
Now reverting SIGINT signal to perform default action
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C