Génaralités sur les signaux
Introduction
Les signaux sont un mécanisme de communication unidirectionnel entre le noyau et les processus ou entre processus. L'arrivée d'un signal interrompt le processus destinataire et déclenche une autre action. Le comportement des signaux classiques d'UNIX est différent d'une version à l'autre ce qui nous emmène donc à utiliser de préférence les mécanismes définis par la norme POSIX qui offrent plus de possibilité de masquer des signaux. Chaque signal a une disposition courante, qui détermine le comportement du processus lorsqu'il reçoit ce signal.
Sur un système donné, on dispose de NSIG signaux numérotés de 1 à NSIG. La constante NSIG, ainsi que les différents signaux et les prototypes des fonctions qui les manipulent, sont définis dans le fichier <signal.h>.
Un signal envoyé par le noyau ou par un autre processus est un signal pendant : cet envoi est mémorisé dans le BCP du processus. Un processus peut changer la disposition d'un signal avec la routine système sigaction() ou (de façon moins portable) ave la fonction signal(). Avec ces appels système, un processus peut choisir de se comporter de l'une des façons suivantes lorsqu'il reçoit ce signal :
effectuer l'action par défaut,
ignorer le signal,
ou rattraper le signal avec un gestionnaire de signal, c'est-à-dire une fonction définie par le programme, qui est invoquée automatiquement lorsque le signal est réçu.A ce niveau le signal peut être masqué par le programme.
Les signaux sont un mécanisme de communication unidirectionnel entre le noyau et les processus ou entre processus. L'arrivée d'un signal interrompt le processus destinataire et déclenche une autre action. Le comportement des signaux classiques d'UNIX est différent d'une version à l'autre ce qui nous emmène donc à utiliser de préférence les mécanismes définis par la norme POSIX qui offrent plus de possibilité de masquer des signaux. Chaque signal a une disposition courante, qui détermine le comportement du processus lorsqu'il reçoit ce signal.
Sur un système donné, on dispose de NSIG signaux numérotés de 1 à NSIG. La constante NSIG, ainsi que les différents signaux et les prototypes des fonctions qui les manipulent, sont définis dans le fichier <signal.h>.
Un signal envoyé par le noyau ou par un autre processus est un signal pendant : cet envoi est mémorisé dans le BCP du processus. Un processus peut changer la disposition d'un signal avec la routine système sigaction() ou (de façon moins portable) ave la fonction signal(). Avec ces appels système, un processus peut choisir de se comporter de l'une des façons suivantes lorsqu'il reçoit ce signal :
effectuer l'action par défaut,
ignorer le signal,
ou rattraper le signal avec un gestionnaire de signal, c'est-à-dire une fonction définie par le programme, qui est invoquée automatiquement lorsque le signal est réçu.A ce niveau le signal peut être masqué par le programme.
signal()
La fonction signal() demande au système de lancer la fonction handler lorsque le signal signum est reçu par le processus courant. La fonction signal() renvoie la fonction qui était précédemment associée au même signal. La fonction handler() prend en paramètre le numéro du signal reçu, et ne renvoie rien. Il y a une trentaine de signaux différents, La liste complète des signaux, leur signification et leur comportement sont décrits dans la page de manuel signal de Chaque UNIX ou de LINUX; e nvoici quelques exemples :
* SIGINT (program interrupt, émis par Ctrl-C),
* SIGTST (terminal stop, émis par Ctrl-Z)
* SIGTERM (demande de fin de processus)
* SIGKILL (arrêt immédiat de processus)
* SIGFPE (erreur arithmétique),
* SIGALRM (fin de délai, voir fonction alarm()), etc.
#include <stdio.h> void (*signal(int signum, void (*handler)(int)))(int);
* SIGINT (program interrupt, émis par Ctrl-C),
* SIGTST (terminal stop, émis par Ctrl-Z)
* SIGTERM (demande de fin de processus)
* SIGKILL (arrêt immédiat de processus)
* SIGFPE (erreur arithmétique),
* SIGALRM (fin de délai, voir fonction alarm()), etc.
Défini dans <signal.h>, Le type sigset_t représente les ensembles de signaux.
La fonction sigemptyset() crée un ensemble vide.
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
La délivrance des signaux
Lorsqu'un processus est en sommeil et qu'il reçoit plusieurs signaux :
* Aucune mémorisation du nombre de signaux reçus .
*Aucune mémorisation de la date de réception d'un signal : les signaux seront traités ultérieurement par ordre de numéro.
*Aucun moyen de connaître le PID du processus émetteur du signal
Attention:
Les signaux sont asynchrones : la délivrance des signaux non masqués a lieu un « certain temps » après leur envoi, quand le processus récepteur passe de l'état actif noyau à l'état actif utilisateur. Cela explique pourquoi un processus n'est pas interruptible lorsqu'il exécute un appel système.
Lorsqu'un processus est en sommeil et qu'il reçoit plusieurs signaux :
* Aucune mémorisation du nombre de signaux reçus .
*Aucune mémorisation de la date de réception d'un signal : les signaux seront traités ultérieurement par ordre de numéro.
*Aucun moyen de connaître le PID du processus émetteur du signal
Attention:
Les signaux sont asynchrones : la délivrance des signaux non masqués a lieu un « certain temps » après leur envoi, quand le processus récepteur passe de l'état actif noyau à l'état actif utilisateur. Cela explique pourquoi un processus n'est pas interruptible lorsqu'il exécute un appel système.
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
La primitive kill
Exemples
Le processus père teste l'existence de son fils avant de lui envoyer le signal SIGUSR1
#include <sys/types.h> #include <signal.h> int kill(pid_t pid,int sig) ;
Le processus père teste l'existence de son fils avant de lui envoyer le signal SIGUSR1
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <signal.h> int main(void) { pid_t pid,pidBis; int status; switch(pid = fork()) { case (pid_t)0: while(1) sleep(1); default: sleep(10); if(kill(pid,0) == -1) { printf("processus fils %d inexistant",pid); exit(1); } else { printf("envoi de SIGUSR1 au fils %d",pid); kill(pid,SIGUSR1); } pidBis=wait(&status); printf("Mort de fils %d avec status=%d",pidBis,status); exit(0); } }
Exemples
1 /* sig.c */ 2 #include <stdlib.h> 3 #include <signal.h> 4 #include <errno.h> 5 #include <unistd.h> 6 #include <stdio.h> 7 #define DELAI 1 /* secondes */ 8 void traitement(int numero_signal) 9 { 10 printf("Signal %d => ", numero_signal); 11 switch (numero_signal) { 12 case SIGTSTP: 13 printf("Je m'endors....\n"); 14 kill(getpid(), SIGSTOP); /* auto-endormissement */ 15 printf("Je me réveille !\n"); 16 signal(SIGTSTP, traitement); /* repositionnement */ 17 break; 18 case SIGINT: 19 case SIGTERM: 20 printf("Fin du programme.\n"); 21 exit(EXIT_SUCCESS); 22 break; 23 } 24 } 25 int main(void) 26 { 27 signal(SIGTSTP, traitement); /* si on reçoit contrôle-Z */ 28 signal(SIGINT, traitement); /* si contrôle-C */ 29 signal(SIGTERM, traitement); /* si kill processus */ 30 while (1) { 31 sleep(DELAI); 32 printf("."); 33 fflush(stdout); 34 } 35 printf("fin\n"); 36 exit(EXIT_SUCCESS); 37 }
La primitive sigprocmask
#ffff00; Cette primitive permet l'installation manuelle d'un masque à partir de l'ensemble pointé par pnew et éventuellement du masque antérieur que l'on récupère au retour de la primitive à l'adresse old si le troisième paramètre n'est pas le pointeur nul. Le paramètre opt précise ce que l'on fait avec ces ensembles .
#include <signal.h> int sigprocmask(int opt,const sigset_t *pnew,sigset_t old) ;
La primitive sigpending
Ecrit à l'adresse ens la liste des signaux pendants qui sont masqués.
Exemple
Pour la suppression du masquage, on pouvait garder le même ensemble ens1 et faire : sigprocmask(SIG_UNBLOCK,&ens1,&ens1);
#include <signal.h> int sigpending(sigset_t *ens) ;
Exemple
#include <stdio.h> #include <signal.h> #include <stdlib.h> #include <unistd.h> int main(void) { sigset_t ens1,ens2; int sig; /* Construction ensemble {SIGINT,SIGQUIT,SIGUSR1} */ /* sigemptyset initialise l'ens. de signaux à vide */ sigemptyset(&ens1); sigaddset(&ens1,SIGINT); sigaddset(&pens1,SIGQUIT); sigaddset(&ens1,SIGUSR1); /* Mise en place du masquage des signaux de cet ens. */ sigprocmask(SIG_SETMASK,&ens1,(sigset_t *)0); printf("Masquage mis en place.\n"); sleep(15); /*Lecture des signaux envoyés mais non délivrés car masqués*/ sigpending(&ens2); printf("Signaux pendants:\n"); for(sig=1;sig<NSIG;sig++) if(sigismember(&ens2,sig)) printf("%d \n",sig); sleep(15); /* Suppression du masquage des signaux */ sigemptyset(&ens1); printf("Déblocage des signaux.\n"); sigprocmask(SIG_SETMASK,&ens1,(sigset_t *)0); sleep(15); printf("Fin normale du processus\n"); exit(0); }
sigaction()
La fonction sigaction() change l'action qui sera exécutée lors de la réception d'un signal. Cette action est décrite par une structure struct sigaction
* sa_handler indique l'action associée au signal signum. Il peut valoir SIG_DFL (action par défaut), SIG_IGN (ignorer), ou un pointeur vers une fonction de traitement de lu signal.
* le masque sa_mask indique l'ensemble de signaux qui seront bloqués pendant l'exécution de ce signal. Le signal lui-même sera bloqué, sauf si SA_NODEFER ou SA_NOMASK figurent parmi les flags.
Le champ sa_flags contient une combinaison d'indicateurs, parmi lesquels
* SA_NOCLDSTOP pour le signal SIGCHLD, ne pas recevoir la notification d'arrêt des processus fils (quand les processus fils reçoivent SIGSTOP, SIGTSTP, SIGTTIN ou SIGTTOU).
* SA_ONESHOT ou SA_RESETHAND remet l'action par défaut quand le handler a été appelé (c'est le comportement par défaut du signal() classique).
* SA_SIGINFO indique qu'il faut utiliser la fonction sa_sigaction() à trois paramètres à la place de sa_handler().
Exemples
#include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); /* non utilisé */ }
* le masque sa_mask indique l'ensemble de signaux qui seront bloqués pendant l'exécution de ce signal. Le signal lui-même sera bloqué, sauf si SA_NODEFER ou SA_NOMASK figurent parmi les flags.
Le champ sa_flags contient une combinaison d'indicateurs, parmi lesquels
* SA_NOCLDSTOP pour le signal SIGCHLD, ne pas recevoir la notification d'arrêt des processus fils (quand les processus fils reçoivent SIGSTOP, SIGTSTP, SIGTTIN ou SIGTTOU).
* SA_ONESHOT ou SA_RESETHAND remet l'action par défaut quand le handler a été appelé (c'est le comportement par défaut du signal() classique).
* SA_SIGINFO indique qu'il faut utiliser la fonction sa_sigaction() à trois paramètres à la place de sa_handler().
Exemples
1 /* sig-posix.c */ 2 #include <stdlib.h> 3 #include <signal.h> 4 #include <errno.h> 5 #include <unistd.h> 6 #include <stdio.h> 7 #define DELAI 1 /*secondes */ 8 #define NB_ITERATIONS 60 9 void traiter_signal(int numero_signal) 10 { 11 struct sigaction rien, ancien_traitement; 12 printf("Signal %d => ", numero_signal); 13 switch (numero_signal) { 14 case SIGTSTP: 15 printf("J'ai reçu un SIGTSTP.\n"); 16 /* on désarme le signal SIGTSTP, avec sauvegarde de 17 du "traitant" précédent */ 18 rien.sa_handler = SIG_DFL; 19 rien.sa_flags = 0; 20 sigemptyset(&rien.sa_mask); /* rien à masquer */ 21 sigaction(SIGTSTP, &rien, &ancien_traitement); 22 printf("Alors je m'endors....\n"); 23 kill(getpid(), SIGSTOP); /* auto-endormissement */ 24 printf("On me réveille ?\n"); 25 /* remise en place ancien traitement */ 26 sigaction(SIGTSTP, &ancien_traitement, NULL); 27 printf("C'est reparti !\n"); 28 break; 29 case SIGINT: 30 case SIGTERM: 31 printf("On m'a demandé d'arrêter le programme.\n"); 32 exit(EXIT_SUCCESS); 33 break; 34 } 35 } 36 int main(void) 37 { 38 struct sigaction a; 39 int i; 40 a.sa_handler = traiter_signal; /* fonction à lancer */ 41 sigemptyset(&a.sa_mask); /* rien à masquer */ 42 sigaction(SIGTSTP, &a, NULL); /* pause contrôle-Z */ 43 sigaction(SIGINT, &a, NULL); /* fin contrôle-C */ 44 sigaction(SIGTERM, &a, NULL); /* arrêt */ 45 for (i = 1; i < NB_ITERATIONS; i++) { 46 sleep(DELAI); 47 printf("%d", i % 10); 48 fflush(stdout); 49 } 50 printf("Fin\n"); 51 return EXIT_SUCCESS; 52 }