Signaux

On peut traiter les signaux de deux façons :

La fonction signal(2) originale d'Unix réinitialisait le gestionnaireà SIG_DFL, comme c'est le cas sous System V. Linux agissait ainsi avec les bibliothèques libc4 et libc5. Au contraire, BSD ne réinitialise pas le gestionnaire, mais bloque les éventuelles nouvelles occurrences du signal durant l'appel de la fonction. La bibliothèque GlibC 2 suit ce comportement. L'utilisation de signal(2) peut causer des problèmes de portabilité. C'est pourquoi il vaut mieux éviter d'utiliser signal(2), et utiliser plutôt sigaction(2).

Exercice 1

Ecrire un programme C qui compte les signaux qu'il reçoit et affiche ces compteurs.

Corrigé

ANSI C

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void handler(int);
int nsig[NSIG];

int main(void)
{  int s;

   for (s = 1; s < NSIG; s++)
   {  if (signal(s, handler) == SIG_ERR)
         fprintf(stdout, "Je ne peux pas attraper signal no %d\n", s);
      nsig[s] = 0;
   }

   while (1) pause();
}


void handler(int s)
{  printf("Signal %d recu %d fois\n", s, ++nsig[s]);
}

POSIX

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void handler(int);
int nsig[NSIG];

int main(void)
{  int s;
   struct sigaction act;

   act.sa_handler = handler;
   sigfillset(&act.sa_mask);
   act.sa_flags = 0;

   for (s = 1; s < NSIG; s++)
   {  if (sigaction(s, &act, NULL) == -1)
         fprintf(stdout, "Je ne peux pas capturer signal no %d\n", s);
      nsig[s] = 0;
   }

   while (1) pause();
}


void handler(int s)
{  printf("Signal %d recu %d fois\n", s, ++nsig[s]);
}

Exemple d'exécution

[stefan@ecrins tp]$ cptsig &
[2] 4640
[stefan@ecrins tp]$ 
Je ne peux pas capturer signal no 9
Je ne peux pas capturer signal no 19
[stefan@ecrins tp]$ kill -USR1 4640
Signal 10 recu 1 fois
[stefan@ecrins tp]$ kill -USR1 4640
Signal 10 recu 2 fois
[stefan@ecrins tp]$ kill -USR1 4640
Signal 10 recu 3 fois
[stefan@ecrins tp]$ kill -CONT 4640
Signal 18 recu 1 fois
[stefan@ecrins tp]$ kill -STOP 4640
[2]+  Stopped                 cptsig
[stefan@ecrins tp]$ kill -CONT 4640
Signal 18 recu 2 fois
[stefan@ecrins tp]$ kill -KILL 4640
[2]+  Processus arrêté      cptsig

Exercice 2

Ecrire un programme C qui exécute une commande avec ses arguments après avoir protégé le processus du signal SIGHUP (envoyé par le noyau quand le terminal auquel le processus est attaché, se déconnecte).

Corrigé

ANSI C

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv)
{  if (argc < 2)
   {  fprintf(stderr,"Usage: %s commande [arg] [arg] ...\n", argv[0]);
      exit(1);
   }
   if (signal(SIGHUP, SIG_IGN) == SIG_ERR)
   {  perror("signal"); exit(1);
   }
   execvp(argv[1], argv + 1);
   perror("execvp");
   exit(1);
}

POSIX

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv)
{  struct sigaction act;

   if (argc < 2)
   {  fprintf(stderr,"Usage: %s commande [arg] [arg] ...\n", argv[0]);
      exit(1);
   }
   act.sa_handler = SIG_IGN;
   sigemptyset(&act.sa_mask);
   act.sa_flags = 0;
   if (sigaction(SIGHUP, &act, NULL) == -1)
   {  perror("sigaction"); exit(1);
   }
   execvp(argv[1], argv + 1);
   perror("execvp");
   exit(1);
}

Exercice 3

Ecrire un programme C qui crée deux processus à l'aide de l'appel système fork(). Le père affichera les entiers pairs compris entre 1 et 100, le fils affichera les entiers impairs compris dans le même intervalle. Synchroniser les processus à l'aide des signaux pour que l'affichage soit 1 2 3 ... 100.

Corrigé

ANSI C

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>


void handler(int sig) {}


int main(void)
{  pid_t pid;
   int i;

   if (signal(SIGUSR1, handler) == SIG_ERR)
   {  perror("signal"); exit(1);
   }
   if ((pid = fork()) == -1)
   {  perror("fork"); exit(1);
   }

   if (pid == 0)
   {  /* fils */
      pid = getppid();
      for (i = 1; i <= 100; i += 2)
      {  printf("%d\n", i);
         kill(pid, SIGUSR1);
         pause();
      }
   }
   else
   {  /* père */
      for (i = 2; i <= 100; i += 2)
      {  pause();
         printf("%d\n", i);
         kill(pid, SIGUSR1);
      }
   }

   return 0;
}

Ce programme, à première vue « innocent », pourrait causer un interblocage. Imaginons le scénario suivant. Le fils est suspendu juste après le kill() et le noyau donne la main au père, qui est en pause(). Le père se réveille, affiche le nombre suivant, émet le signal et s'endormit à nouveau. Le noyau délivre le signal au fils et il reprend son exécution par le handler(), suivi par une pause() qui ne finit jamais. On peut bricoler le remède suivant.

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>


int flag = 1;
void handler(int sig) {flag = 0;}


int main(void)
{  pid_t pid;
   int i;

   if (signal(SIGUSR1, handler) == SIG_ERR)
   {  perror("signal"); exit(1);
   }
   if ((pid = fork()) == -1)
   {  perror("fork"); exit(1);
   }

   if (pid == 0)
   {  /* fils */
      pid = getppid();
      for (i = 1; i <= 100; i += 2)
      {  printf("%d\n", i);
         kill(pid, SIGUSR1);
         if (flag) pause();
         flag = 1;
      }
   }
   else
   {  /* père */
      for (i = 2; i <= 100; i += 2)
      {  if (flag) pause();
         flag = 1;
         printf("%d\n", i);
         kill(pid, SIGUSR1);
      }
   }

   return 0;
}

Est-ce que cette solution marche toujours ?

Exercice 4

Ecrire un programme C qui met en évidence l'héritage des handlers de signaux après le fork().

Corrigé

Dans l'exercice précédent le gestionnaire est installé par le père et hérité par le fils.

Exercice 5

Ecrire un programme C qui utilise 3 processus H, M, S qui incrémentent les 3 « aiguilles » d'une horloge. S reçoit un signal SIGALRM chaque seconde et émet un signal à M quand son compteur passe de 59 à 0. Quand M reçoit un signal, il incrémente son compteur. Quand son compteur passe de 59 à 0, M envoie un signal à H. Les paramètres correspondent aux valeurs d'initialisation des compteurs.

Envoyez votre programme ici. N'oubliez pas à préciser votre nom et prénom.