Linux/Unix : Les signaux entre processus

03 octobre 2013 rdorigny 0 commentaires

Un signal Unix est une information qui est envoyé à un processus. Ensuite, le processus interprète le code transmis et agit en conséquence. Le signal est envoyé à un autre processus (ou à lui même), c'est une technique pour dialoguer entre le père et le fils.

C'est un mécanisme très puissant qui n'est pas assez utilisé au vu de ce qu'il permet de faire.




1) Généralités

Un signal est transmis d'un processus à un autre, et le récepteur avise sur l'action à entreprendre. Il peut ne pas prendre en compte le signal, le capturer vers le gestionnaire de signal ou laisser le système traiter le signal avec le comportement usuel.

Le Système dispose de NSIG signaux numérotés de 1 à NSIG. On retrouve les signaux connus par le système sous /usr/include/signal.h ou /usr/include/asm/signal.h . A noter que la plupart des signaux sont transmis par le noyau et que certains signaux traitent des problématiques de temps réels définis par Posix.

Les premiers signaux de la liste sont les signaux dits classiques (non temps réels), ensuite SIGRTMIN et SIGRTMAX encadre les signaux temps réels lorsqu'ils sont implémentés. Attention, les valeurs des signaux peuvent changer d'une distribution à une autre, aussi il très vivement conseillé de travailler avec les codes plutôt qu'avec les valeurs.

Voici un exemple de contenu du fichier signal.h
/* Here we must cater to libcs that poke about in kernel headers. */ #define NSIG 32 typedef unsigned long sigset_t; #define SIGHUP 1 #define SIGINT 2 #define SIGQUIT 3 #define SIGILL 4 #define SIGTRAP 5 #define SIGABRT 6 #define SIGIOT 6 #define SIGBUS 7 #define SIGFPE 8 #define SIGKILL 9 #define SIGUSR1 10 #define SIGSEGV 11 #define SIGUSR2 12 #define SIGPIPE 13 #define SIGALRM 14 #define SIGTERM 15 #define SIGSTKFLT 16 #define SIGCHLD 17 #define SIGCONT 18 #define SIGSTOP 19 #define SIGTSTP 20 #define SIGTTIN 21 #define SIGTTOU 22 #define SIGURG 23 #define SIGXCPU 24 #define SIGXFSZ 25 #define SIGVTALRM 26 #define SIGPROF 27 #define SIGWINCH 28 #define SIGIO 29 #define SIGPOLL SIGIO /* #define SIGLOST 29 */ #define SIGPWR 30 #define SIGSYS 31 #define SIGUNUSED 31 /* These should not be considered constants from userland. */ #define SIGRTMIN 32 #define SIGRTMAX _NSIG


Voyons la signification des différents signaux:
  • SIGABRT ou SIGIOT: ce signal est lancé par la fonction abort(), le programme doit se terminer,
  • SIGALRM: signal du noyau utilisé par la fonction alarm(), généralement lié à une temporisation
  • SIGBUS: erreur d'alignement des adresses sur le bus,
  • SIGSEGV: violation de segmentation, pointe sur une addresse qui n'appartient pas processus,
  • SIGCHLD: créé par le noyau pour signaler à un processus qu'il a un fils qui s'est terminé, on l'utilise notamment avec le fonction wait() (voir article précédent),
  • SIGPFE: utilisé en cas d'erreur arithmétique ou d'absence de coprocesseur arithmétique,
  • SIGHUP: indique la déconnexion du terminal, utilisé par la fameuse commande kill,
  • SIGILL: indique une erreur de code assembleur, le processus est arrêté,
  • SIGINT: indique une interruption clavier tel que CTRL+C,
  • SIGIO ou SIGPOLL: indique un changement d'état sur un descripteur de fichiers pour autoriser sa lecture,
  • SIGKILL: ce signal ne pas être stoppé, il est pris immédiatement en compte pour stopper le processus, associé à kill -9 numprocess,
  • SIGQUIT: indique une interruption clavier tel que CTRL+, il termine le processus,
  • SIGSTOP ou SIGSTP: stoppe un processus,
  • SIGCONT: reprend un processus stoppé,
  • SIGTERM: demande polie pour arrêter un processus, il peut être annulé,
  • SIGTRAP: transmis sur un point d'arrêt d'un débogueur,
  • SIGTTIN: transmis sur un point d'arrêt d'un débogueur,
  • SIGUSR1 et SIGUSR2: signaux pour le développeur s'il souhaite un échange inter-processus,
  • 2) Envoi et réception des signaux

    Un signal dit pendant est envoyé par un processus ou le noyau. Le signal est dit délivré lorsque le processus réalise l'action:
  • réaliser l'action par défaut,
  • ignorer le signal,
  • réaliser l'action définie par l'usager (hnadle), on dit que le signal est capté dans ce cas.

  • On peut également masquer ou bloquer un signal.

    2.1) Envoi d'un signal

    On utilise la fonction kill() pour envoyer un signal à un processus (en fait ne le tue pas nécessairement), tel que:
    int kill(pid_t pid, int numsignal);

    Il est à noter que la commande kill -l liste les différents signaux du système, ils peuvent différer d'une distribution à une autre c'est pourquoi il est vivement conseillé de coder avec nom des signaux pour avoir un code portable. La commande kill du bash est kill -code num_process.

    Si kill(0) est différent de -1, c'est que le signal est encore vivant, cela permet de tester l'existence d'un signal.

    Un exemple d'utilisation:
    #include <stdio.h> #include <stdlib.h> #include <signal.h> int main(){ pid_t pid,pidbis; int status; switch(pid=fork()){ case (pid_t)0: //pour le fils while(1) sleep(1); default: sleep(15); if (kill(pid,0)==-1){ printf("Le fils %d est mort.n",pid); exit(1); } else{ printf("Envoi de SIGUSR1 au fils.n"); kill(pid,SIGUSR1); } //On attend tranquillement la mort du processus fils pidbis=wait(&status); printf("Mort de %d, status=%d.n",pidbis,status); } exit(0); }




    Par défaut, la fonction kill() termine un processus, donc la commande kill(pid,SIGUSR1); termine le processus fils.

    2.2) Le masquage des signaux

    Il est possible de bloquer des signaux (sauf SIGKILL et SIGSTOP) par l’intermédiaire d'un masque mis en place par la fonction sigprocmask():
    #include <signal.h> int sigprogmask(int methode,const sigset_t *nouveau,sigset_t *ancien);

    nouveau est le nouveau masque et ancien celui d'avant. Le type sigset_t est le type des signaux. S'il n'y a pas de masque ancien à reprendre alors le troisième paramètre est à NULL. Le paramètre opt définit l'action à réaliser en utilisant les constantes prédéfinies:
  • SIG_BLOCK: Fait l'union avec le masque précédent,
  • SIG_UNBLOCK: Retire les masques et donc libèrent les signaux bloqués,
  • SIG_SETMASK: applique un masque de blocage.
  • 2.2) Les fonctions relatives aux signaux

    Le C système pour unix propose des des fonctions permettant de manipuler les signaux
    #include <signal.h> //initialise la liste des signaux pendants masqués int sigprogmask(sigset_t *list); //vide les signaux en attente de la liste passée en paramètre int sigempty(sigset_t *list); //ajoute le signal à la liste des signaux masqués int sigaddset(sigset_t *list,int signum); //Teste si le signal signum appartient à la liste des signaux filtrés int sigismenber(const sigset_t *list,int signum);

    Nous allons créer un set de signaux masqués et voir comment afficher les signaux reçus:
    #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <signal.h> int main(void){ sigset_t ens1,ens2; int i; //Création d'un ensemble de masquage, ou lui indique les signaux à masquer sigemptyset(&ens1); sigaddset(&ens1,SIGQUIT); sigaddset(&ens1,SIGUSR1); //On applique le masque sigprocmask(SIG_SETMASK,&ens1,(sigset_t *)0); printf("On masque pour 30s."); sleep(30); //Affichage des signaux bloqués sigpending(&ens2); printf("Signaux pendants:n"); for (i=1;i<NSIG;i++) if (sigismember(&ens2,i)) printf("%d n",i); //On vide les signaux masqués sigemptyset(&ens1); printf("On débloque les signaux.n"); sigprocmask(SIG_SETMASK,&ens1,(sigset_t *)0); sleep(10); printf("Fin normale du processus!!n"); exit(0); }




    On constate que les signaux transmis comme CTRL + s'affichent par leurs numéros. On également envoyer un kill -3 numPID.

    2.3) Réception des signaux

    Pour capter un signal, on utilise la fonction sigaction() et la structure associée.
    #include <signal.h> struct sigaction{ void (*sa_handler)(); sigset_t sa_mask; int sa_flags } int sigaction(int sig,struct sigaction *p_action,struct sigaction *p_action_anc);

    La fonction retourne 0 en cas de réussite et -1 sinon. Le premier paramètre est le signal, le second symbolise la structure sigaction et le dernier une structure sur l'ancien comportement (permet de le remettre une fois terminé). Pour la structure sigaction, le sa_handler peut prendre les valeurs:
  • SIG_DL: demande au noyau de remettre le comportement par défaut du signal,
  • SIG_IGN: demande au noyau d'ignorer le signal,
  • pointeur sur la fonction qui donne le comportement si réception du signal.

  • Enfin, sa_mask indique le set de signaux concernés par l'action et sa_flags qui est un OU binaire des différentes constantes permettant de gérer le comportement du gestionnaire de signal.

    Vous trouverez ci-dessous un exemple de code qui détourne le comportement normal du signal SIGQUIT et SIGINT:
    #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <signal.h> sigset_t ens; struct sigaction action; void handler(int sig); int main(void) { action.sa_handler=handler; sigemptyset(&action.sa_mask); //Applique le captage sur les signaux SIGQUIT et SIGINT sigaction(SIGQUIT,&action,(struct sigaction *)0); sigaddset(&action.sa_mask,SIGQUIT); sigaction(SIGINT,&action,(struct sigaction *)0); while(1) sleep(1); } void handler(int sig) { int i; printf("Entrée dans le handler avec le signal: %dn",sig); sigprocmask(SIG_BLOCK,(sigset_t *)0,&ens); printf("Signaux bloqués: "); for(i=1;i<NSIG;i++) { if(sigismember(&ens,i)) printf("%d "); } putchar('n'); if(sig == SIGINT) { action.sa_handler=SIG_DFL; sigaction(SIGINT,&action,NULL); /* Comportement standard rétabli. */ } printf("Sortie du handlern"); }


    Ce qui donne en faisant un CTRL+C:


    2.3) Attente d'un signal

    #include <signal.h> int sigsuspend(const sigset_t *ens);

    La fonction sigsuspend() réalise le masquage des signaux pointé par ens et la mise en sommeil jusqu'à l'arrivée d'un signal non-masqué ce qui provoque la mort du processus ou l'exécution du handler mis pour ce signal.

    3) Les signaux temps-réel

    Les signaux temps réels sont implémentés avec la norme POSIX. A la différence des autres signaux, ils n'ont pas de signification particulière. Les signaux temps-réel sont compris entre SIGRTMIN et SIGRTMAX, attention le noyau ou d'autres applications peuvent utiliser les premiers signaux disponibles.

    Les caractéristiques des signaux temps réels:
  • les signaux sont empilés,
  • il est possible de les faire accompagner d'une valeur,
  • il est possible de connaitre le PID du processus émetteur,
  • le numéro de signal correspond à la priorité de traitement dans la pile.

  • Pour transmettre des données avec le signal, il est nécessaire d'activer le flag SA_SIGINFO dans la structure sigaction et d'utiliser la fonction :
    int sigqueue(pid_t pid_destinataire, int num_signal, const union sigval valeur)); union sigval{ int sigval_int; void *sigval_ptr; }

    Plutôt qu'un long discours, voici un exemple qui reprend tout ce que nous avons vu:
    #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> void gestionnaire(int sig, siginfo_t *info, void *inutile) ; int main() { pid_t pid_fils; int info_fin; union sigval valeur; valeur.sival_int=10; struct sigaction action; switch ( pid_fils = (long)fork() ){ case -1: perror("Erreur lors de fork"); return EXIT_FAILURE; case 0: action.sa_sigaction = gestionnaire; action.sa_flags = SA_SIGINFO; sigaction(SIGUSR1, &action, NULL); while ( 1 ){ printf("Le processus fils est vivantn"); sleep(2); } default: sleep(5); printf("Père envoie SIGUSR1 à son filsn"); if ( kill(pid_fils, SIGUSR1) ) { perror("kill "); exit(EXIT_FAILURE); } sleep(2); if ( sigqueue(pid_fils, SIGUSR1, valeur) ) { perror("sigqueue "); exit(EXIT_FAILURE); } sleep(2); if ( kill(pid_fils, SIGKILL) ) { perror("kill "); exit(EXIT_FAILURE); } } } void gestionnaire(int sig, siginfo_t *info, void *inutile) { char *origine; printf("Reception du signal n° %d ", sig); switch ( info->si_code ){ case SI_USER: printf("envoyé au moyen de kill() ou raise() par le processus %ldn", info->si_pid); break; case SI_QUEUE: printf("envoyé au moyen de sigqueue() par le processus %ld avec la valeur %dn", (long)info->si_pid,info->si_value.sival_int); break; default: printf("n"); break; } }



    Conclusion

    Les signaux représentent un des moyens de discuter entre les différents processus, et nous avons vu qu'il est possible de transmettre des données, de tuer un processus ou encore de faire milles choses...







    Pseudonyme (obligatoire) :
    Adresse mail (obligatoire) :
    Site web :

    Pour valider votre commentaire, écrivez le texte affiché sur l'image :



    © 2017 www.doritique.fr par Robert DORIGNY