BCI INF Module ARSE

TP Signaux UNIX

B. Dupouy et S. Gadret

SOMMAIRE

Organisation du TP

1ère partie Exemples à faire fonctionner
2ème partie Exercice à rendre. L'envoyer par mail à l'enseignant
qui vous fait les cours, par exemple : dupouy@inf
Le texte du sujet ( subject ) doit contenir le mot :
TPX pour les X-IGE1
TPAST pour les AST maîtrises
TP1A pour les éléves de 1ère année

  1. Présentation des signaux
  2. Ignorer les signaux
  3. Traitement spécifique des signaux
  4. Traitement des erreurs sur les accès mémoire
  5. EXERCICE : Utilisation de SIGUSR1 et SIGUSR2

1. Présentation des signaux

Les signaux ont des origines diverses, ils peuvent être :

  1.  retransmis par le noyau : division par zéro, overflow, instruction interdite,
  2.  envoyés depuis le clavier par l'utilisateur ( touches : <CTRL>Z, <CTRL>C, <CTRL>\.. )...
  3. émis par la commande kill depuis le shell ou depuis le C par l'appel à la primitive kill
Emission :

kill (num_du_processus, num_du_signal) en C,
kill -num_du_signal num_du_processus, en Shell.

Remarque : l'émetteur ne peut pas savoir si le destinataire a reçu ou non le signal.

Réception, comportements possibles du destinataire du signal :

ignorer le signal signal(num_du_signal,SIG_IGN)
repositionner le traitement par défautsignal(num_du_signal,SIG_DFL)
définir un traitement spécifique signal(num_du_signal,fonction)

L'ensemble des signaux est décrit dans /usr/include/sys/signal.h ou dans /usr/include/bits/signum.h.

Remarques :

  1. Sur les UNIX de la famille Berkeley, par exemple Solaris de Sun, après exécution de la fonction spécifique définie par l'utilisateur, l'option par défaut est rétablie. Si on veut conserver l'option spécifique il faut rappeler signal(num_sig, fonction) dans fonction.
  2. On n'a pas le droit de changer le traitement par défaut de certains signaux, tel que SIGKILL.
Sur une machine multi-processeur on peut avoir à forcer l'exécution sur un seul processeur, dans ce cas il faut utiliser la commande taskset, par exemple :
taskset -c 0 ./exo1

2.Ignorer les signaux

Ecrire un programme qui ignore TOUS les signaux.

Le schéma de programmation est donné ci-dessous. Le rôle du while(1) est de boucler pour attendre la réception d'un signal.

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main(void){
  int Nb_Sig;
  for(Nb_Sig = 1; Nb_Sig < NSIG ; Nb_Sig ++){
      ...
      ...
  }
  while(1){
     sleep(5);
  }  /* Attendre des signaux   */
  return 0;
}
A FAIRE :
  1.  Tester la valeur de retour de la fonction signal pour relever les (rares) signaux qu'on ne peut ignorer.
    Consulter /usr/include/sys/iso/signal_iso.h (ou /usr/include/bits/signum.h) qui est inclus par #include <signal.h> pour identifier le signal en fonction de son numéro (Nb_Sig).
  2.  Faire <CTRL>C dans la fenêtre où tourne le programme. Envoyer également des signaux vers ce programme en utilisant kill depuis une autre fenêtre. Constater que SIGKILL (signal numéro 9) termine ce programme.

3. Traitement spécifique des signaux

Modifier le programme précédent : les signaux ne seront plus ignorés, mais traités par une fonction spéciale, que l'on appellera Traite_Sig.

Le schéma de programmation est donné ci-dessous. Le rôle du while(1) est de boucler pour attendre la réception d'un signal.

#include <signal.h>
#include <unistd.h>
int main(void){
void Traite_Sig(int Numero);
int Nb_Sig ;

for(Nb_Sig = 1; Nb_Sig < NSIG ; Nb_Sig ++){
     ...
     ...
    }
    while(1){
    sleep(5);  /* Attendre des signaux   */
    }
}

/************* Fonction de traitement **************/
void Traite_Sig (int Numero){
    printf("Coucou, recu signal %d !\n", Numero);
    ...
}
Faire comme précédemment, c'est à dire :

  1. Tester la valeur de retour de la fonction signal pour relever les (rares) signaux pour lesquels on ne peut définir un traitement spécifique.
  2. Faire <CTRL>C dans la fenêtre où tourne le programme. Envoyer des signaux vers ce programme en utilisant kill depuis une autre fenêtre. Constater que le signal SIGKILL (9) termine ce programme.

Attention :
Sur les UNIX de la famille Berkeley, par exemple Solaris de Sun, après exécution de la fonction spécifique définie par l'utilisateur, l'option par défaut est rétablie. Si on veut conserver l'option spécifique il faut rappeler signal(num_sig, fonction) dans fonction. Ceci n'est pas utile sous LINUX, qui est un UNIX à base System V.

4. Traitement des erreurs sur les accès mémoire

Une tentative d'accès à un zone mémoire interdite se traduit par l'envoi d'un signal SIGSEGV ou SIGBUS au processus fautif.
La réception de l'un de ces signaux provoque la fin du processus.
Pour éviter cette fin, mais être avertis des éventuelles erreurs, nous allons écrire un programme qui provoque ce type d'erreur et traite de façon spécifique les signaux reçus.

Pour récupérer le canevas du programme, cliquer ici.

Questions :

  1. Dans main :
    1. Compléter les appels à la fonction signal
    2. Positionner le point de reprise en utilisant sigsetjmp plutôt que setjmp (cf. cours et man sigsetjmp), Lors de l'appel à la fonction sigsetjmp on mettra le second paramètre à un.
  2. Compléter la fonction de traitement des signaux Traite_sig pour que :
    1. Elle soit appelée à chaque occurrence d'un signal, et pas seulement lors de la première.
      On vérifiera la valeur de retour de la fonction signal.
    2. Le retour de cette fonction se fasse au sigsetjmp se trouvant dans main (utiliser siglongjmp ). Lors de l'appel à la fonction siglongjmp on initialisera le second paramètre avec le numéro du signal qui a provoqué l'erreur.
  3. Exécuter le programme et vérifier que l'on passe bien par Traite_Sig pour chaque occurence de l'erreur,
Remarque : on constate qu'il n'y a pas vraiment de type "tableau" en C...

5. EXERCICE : Utilisation de SIGUSR1 et SIGUSR2

Ecrire un programme (canevas donné plus bas) qui :

  1.  Affiche son numéro (pid) via l'appel à getpid(),
  2.  Traite tous les signaux par une fonction fonc qui se contente d'afficher le numéro du signal reçu.
  3.  Traite le signal SIGUSR1 par une fonction fonc1 et le signal SIGUSR2 par fonc2 :

A FAIRE :

Schéma du programme :

#include  <signal.h>
#include <unistd.h>
int main (void){
   ...
   /* Mettre ici le traitement pour tous les signaux 
      sauf SIGUSR1 et SIGUSR2.  */
   ...
   /* Mettre ici le traitement pour SIGUSR1 et SIGUSR2 */
   ...
   while (1){
     printf("main : pid %d attend des  signaux \n", (int)getpid());
     sleep(5);
   }   /* Attendre les signaux   */
}

/*************** La fonction fonc **************/
void fonc (int NumSignal){
...
}
/*************** La fonction fonc1 **************/
void fonc1 (int NumSignal){
...
}
/*************** La fonction fonc2 **************/
void fonc2 (int NumSignal){
...
}




©(Copyright) dupouy@inf.enst.fr