Signaux

Interception de signaux

Écrire un programme qui cherche à intercepter tous les signaux de 1 à (_NSIG - 1), puis se met en attente dans une boucle infinie. Pour chaque signal reçu, le programme devra afficher un message contenant le numéro du signal reçu.
Tester le programme en lui envoyant des signaux, soit avec la commande kill, soit via des raccourcis du terminal (Ctrl-C, Ctrl-\, Ctrl-Z, ...).
Vous écrirez quatre versions de votre programme :

  1. en utilisant la fonction signal(2) ou sigaction(2) ;
  2. en implémentant les deux sémantiques vues en cours (réarmement automatique ou non).

Synchronisation de processus

On souhaite afficher 10 lignes "IUT Belfort Montbéliard" à l'aide de 3 processus.  Pour cela, on crée 3 processus fils : Fils1, Fils2 et Fils3. Chaque processus se met en attente de récecption du signal SIGUSR1.

À réception du signal SIGUSR1 :

  • le processus Fils3 affiche "IUT ", puis envoie un signal SIGUSR1 à Fils2 ;
  • le processus Fils2 affiche "Belfort ", puis envoie un signal SIGUSR1 à Fils1 ;
  • le processus Fils1 affiche "Montbéliard\n", puis envoie un signal SIGUSR1 au processus père ;
  • le processus père vérifie si le nombre de messages a été atteint, sinon il envoie un signal SIGUSR1 à Fils3.

Le père commence par envoyer un signal SIGURS1 à Fils3. Lorsque le nombre de messages a été atteint, le père tue les processus fils et attend leur fin avant de se terminer lui-même.

Redirections (partie facultative)

Écrire un programme :

execute [-i entrée] [-o sortie] [-e erreur] [--] commande [args...]

qui exécute la commande passée en paramètre en redirigeant, lorsque les options sont spécifiées:

  • l'entrée standard depuis le fichier "entrée"
  • la sortie standard vers le fichier "sortie"
  • la sortie d'erreur vers le fichier "erreur"

Vous écrirez deux versions de votre programme:

  1. une version utilisant les fonctions de haut-niveau (fopen(), freopen(), ...)
  2. une version utilisant les fonctions de bas niveau (open(), dup(), dup2(), ...)

NB:

  • la solution proposée ne devra bien sûr pas utiliser les fonctions system(3) ou popen(3) !
  • pour analyser la ligne de commande, vous pouvez utiliser la fonction getopt(3).

Compléments sur les signaux

Description

Dans un processus, il est possible d'adopter trois comportements à la réception d'un signal :

  • le comportement par défaut, tel que décrit dans la page de manuel signal(7) : SIG_DFL ;
  • ignorer le signal : SIG_IGN ;
  • exécuter une fonction utilisateur : gestionnaire ou handler pour le signal.

Si un gestionnaire de signal est mis en place et que le signal en question est déclenché, deux modes de fonctionnement peuvent exister :

  • fonctionnement dit « à la SysV » : le comportement pour le signal est réinitialisé à SIG_DFL avant d'exécuter la fonction handler.
  • fonctionnement dit « à la BSD » : le comportement pour le signal n'est pas modifié; le handler reste en place.

Mise en œuvre

Pour modifier le comportement, il est possible d'utiliser deux fonctions :

  • signal(2) : historique, plus simple utiliser, mais son usage est déconseillé, sauf pour SIG_DFL et SIG_IGN.
    En effet, pour un gestionnaire de signal, le mode de fonctionnement dépend de l'implémentation (version du système, options de compilation, ...).
  • sigaction(2) : définie par la norme POSIX, son usage doit être privilégié.
    Pour un gestionnaire de signal, c'est le mode de fonctionnement « à la BSD » qui est adopté. L'option de sigaction SA_RESETHAND permet de choisir un fonctionnement « à la SysV ».

Exemples

Voici quelques exemples d'utilisation, pour le signal SIGUSR1. Soit la fonction suivante, utilisée comme gestionnaire

void machin(int sig) { printf("caught signal %d\n", sig); }

Avec signal

Synopsis
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
Usage
signal(SIGUSER1, SIG_DFL); // comportement par défaut

signal(SIGUSER1, SIG_IGN); // ou bien, ignorer le signal

signal(SIGUSER1, machin);  // ou bien, utilisation de machin()
Remarque

Par défaut sous Linux, avec les versions actuelles de la glibc, c'est le fonctionnement « à la BSD » qui est adopté. Pour sélectionner un fonctionnement « à la SysV » on peut ajouter, avant tout #include, la ligne :

#define _POSIX_C_SOURCE 200809L

Avec sigaction

Synopsis
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
Usage
struct sigaction act;
act.sa_flags = 0;          // options, cf. manuel
sigemptyset(&act.sa_mask); // initialisation à l'ensemble vide

act.sa_handler = SIG_DFL; // comportement par défaut
act.sa_handler = SIG_IGN; // ou bien, ignorer le signal
act.sa_handler = machin;  // ou bien, utilisation de machin()

sigaction(SIGUSR1, &act, NULL);