Travaux pratique d'informatique Temps Réel,, TP 2 1 2.Les signaux et les alarmes 2.1.Objectifs Cette séance permettra de découvrir un autre moyen de faire communiquer des processus : l'utilisation des signaux. Cette fois ce sont des notifications (signaux) et non des données qui sont transmises. Les manipulations à faire sont notamment : envoi et réception de signaux; modification du comportement du processus à la réception d'un signal. Les alarmes et les minuteurs permettront d'intégrer la notion de temps par la génération d'un signal particulier (e.g. SIGALRM) périodiquement et/ou après un temps déterminé. 2.2.Généralités sur les signaux Les signaux sont des notifications générées au sein d'un processus pour l'avertir de l'occurrence d'un événement déterminé. Les signaux sont produits soit manuellement (envoi explicite d'un signal d'un processus à un autre), soit par le système (par exemple lors d'une violation d espace mémoire, d'une erreur arithmétique, d'instructions illicites). Les macros courantes relatives aux signaux sont définies dans le fichier d'en tête <signal.h>. En annexe les principaux signaux sont énumérés. a) Envoi d'un signal La fonction suivante est utilisée pour envoyer un signal : int kill(int pid, int n_signal) Le système envoie le signal n_signal au processus d'identifiant pid. Le paramètre n_signal est une constante entière soit définie par l'utilisateur, soit choisie parmi celles du système relative aux signaux (par exemple SIGKILL ou SIGUSR1). Si pid vaut 0, le signal est alors envoyé à tous les processus de son groupe. Cet appel renvoie 0 en cas de réussite et 1 sinon (auquel cas il affecte la variable système errno qui permet la génération automatique d'un message d'erreur, à l'aide de perror). Par exemple, pour envoyer le signal SIGUSR1 au processus de pid 7232: if(kill(7232, SIGUSR1) == 1) { perror("erreur"); } Remarque : un signal peut être envoyé à partir du terminal (voir la commande kill). b)comportement à la réception d'un signal Par défaut, la réception d'un signal est associée à un comportement donné (différent selon le type de signal). Un programme peut modifier ce comportement par défaut à l'aide de la fonction sigaction et spécifier un déroutage, c'est à dire un appel à une fonction spécifique lors de la réception d'un signal. Cet appel n'est ni synchronisé avec l'envoi du signal, ni bloquant. Une fois ce nouveau comportement défini, lorsque dans la suite du programme, le processus reçoit le signal, il interrompt son exécution «normale» et fait appel à la fonction de déroutage. Celle ci réalise un certain nombre d'opérations après quoi le processus reprend son exécution à l'endroit où il a été interrompu.
Travaux pratique d'informatique Temps Réel,, TP 2 2 int sigaction(int n_signal,const struct sigaction * nouveau_comportement,struct sigaction * ancien_comportement) Cette fonction reçoit en paramètre, le numéro du signal (n_signal) auquel on souhaite définir un nouveau comportement. Le deuxième paramètre est une structure sigaction spécifie le nouveau comportement. L'ancien comportement peut éventuellement être récupéré dans une structure de même type (sigaction) passée comme troisième paramètre. Cette structure sigaction, définie dans la librairie signal.h, reprend principalement trois informations utiles: struct sigaction { sighandler_t sa_handler; sigset_t sa_mask; int sa_flags;... }; sa_handler spécifie la fonction à exécuter en cas de réception du signal et peut prendre une des trois valeurs suivantes: SIG_DFL : pointeur vers la fonction appelée par défaut lors de la réception du signal; SIG_IGN : pointeur vers une fonction système qui permet d'ignorer le signal (sauf pour les signaux SIGKILL et SIGSTOP qui ne peuvent pas être ignorés); fonction : fonction définie par le programmeur. sa_mask correspond à un ensemble de signaux qui seront bloqués pendant toute l'exécution de la fonction de déroutage. Cet ensemble de signaux peut être manipulé à l'aide des appels suivants: sigemptyset(sigset_t * ensemble); // Vide l'ensemble de signaux sigfillset(sigset_t * ensemble); // Remplit l'ensemble avec tous les signaux connus sigaddset(sigset_t * ensemble, int n_signal); // Ajoute le signal n_signal à l'ensemble sigdelset(sigset_t * ensemble, int n_signal); // Retire le signal n_signal de l'ensemble sigismember(sigset_t * ensemble, int n_signal); // Test l'appartenance de n_signal à l'ensemble sa_flags permet de spécifier un ensemble d'options de configuration qui ne seront pas décrites ici (on fera donc sa_flags=0). L'exemple ci dessous montre le déroutage vers une fonction deroute lors de la réception du signal SIGUSR1: struct sigaction sa; sa.sa_handler = deroute; sigfillset(&sa.sa_mask); // Bloquer tous les signaux // Aucune option particulière // Si récupérer l'ancien comportement ne nous // intéresse pas, on peut placer le troisième // paramètre à NULL Après cet appel, si le processus reçoit un signal SIGUSR1, il arrête son traitement en cours pour sa.sa_flags=0; sigaction(sigusr1, &sa, NULL); exécuter la fonction deroute. 2.3.Généralités sur les alarmes et les minuteurs a) Les alarmes Les signaux SIGALRM, SIGVTALRM et SIGPROF sont des signaux particuliers qui sont gérés en fonction du temps. Une alarme peut être mise en place à l'aide de la fonction suivante : unsigned int alarm(unsigned int s)
Travaux pratique d'informatique Temps Réel,, TP 2 3 Elle possède les caractéristiques suivantes : elle provoque l'émission d'un signal SIGALRM après s secondes; elle renvoie le nombre de secondes qui restaient avant le déclenchement de l'éventuelle alarme précédemment définie; si s vaut 0, l'alarme en cours est annulée; le traitement de l'alarme doit être défini (à l'aide de la fonction signal), sinon le processus est tué à la réception de SIGALRM. b)les minuteurs La gestion du temps dans les processus peut être réalisée plus précisément avec les fonctions setitimer et getitimer. Le temps est alors défini en secondes et microsecondes, qu'il s'agisse du temps réel (elapsed time) ou du temps effectif (temps d'exécution du processus). #include <sys/time.h> setitimer(int which, struct itimerval *value, struct itimerval *ovalue) getitimer(int which, struct itimerval *ovalue) La première fonction installe un minuteur, alors que la seconde en récupère les valeurs. Le premier paramètre which spécifie le type de temps concerné et conditionne le type de signal émis. Valeur de which Type de temps Description Signal émis ITIMER_REAL temps réel Temps réel sans tenir compte de l'exécution du processus (temps écoulé) SIGALRM ITIMER_VIRTUAL temps virtuel Temps évalué uniquement lorsque le processus tourne (temps CPU propre) SIGVTALRM ITIMER_PROF temps virtuel et système Temps évalué lorsque le processus tourne ou lorsque le système exécute des tâches pour le compte du processus (temps CPU global) SIGPROF La durée de l'alarme peut être spécifiée à l'aide du paramètre value. Le paramètre ovalue permet d'obtenir la durée restante de l'alarme précédemment fixée (si cette valeur n'est pas intéressante dans le programme, ce paramètre peut être NULL). Les paramètres value et ovalue sont définis sur base d'une structure itimerval. Celle ci contient les valeurs suivantes : struct itimerval { struct timeval it_interval; struct timeval it_value; } // durée entre 2 alarmes // durée avant la première alarme (offset) Chaque structure timeval permet de spécifier le nombre de secondes et de microsecondes à compter : struct timeval { long tv_sec; long tv_usec; } // secondes // microsecondes Si it_value est NULL, le minuteur est annulé. Si it_interval est NULL, le prochain déclenchement est annulé.
Travaux pratique d'informatique Temps Réel,, TP 2 4 2.4.Exercice 1 La réalisation de cet exercice se fera en plusieurs étapes: 1. Créer un programme qui tourne indéfiniment et qui affiche en boucle un message quelconque toutes les secondes. 2. Tester les effets de CTRL C et CTRL Z (il est important de vérifier à chaque fois l'état des processus avec la commande ps). 3. Modifier le programme pour qu'il ignore les CTRL C (correspond à la génération d'un signal SIGINT). 4. Modifier le programme pour qu'il affiche un message déterminé toutes les 5 secondes en faisant usage de la fonction alarm(). Remarque : il est possible de tuer un processus en utilisant la commande kill ou killall. 2.5.Exercice 2 Écrire un programme qui réalise un compte à rebours de précision. Il reçoit en paramètre le nombre (entier) de secondes qu'il doit décompter et affiche la valeur du compteur tous les dixièmes de seconde jusqu'à expiration. Par exemple : $./rebours 5 2.6.Exercice 3 Un programme père crée un fils. Le fils boucle indéfiniment en demandant à l'utilisateur s'il veut envoyer un signal à son père. Lorsque l'utilisateur le souhaite, le fils envoie un signal SIGUSR1 au père qui demande au fils de se terminer. Par exemple: Fils Envoyer un signal? (y/n) n Fils Envoyer un signal? (y/n) y Pere J'ai recu un signal, je tue mon fils 2.7.Exercice 4 Écrire un programme qui crée deux fils, lit des entiers non nuls et transmet les entiers négatifs au fils de gauche et les entiers positifs au fils de droite en utilisant un seul tube. Lorsqu'il reçoit 0, le père signale aux deux fils la fin de la saisie et ces derniers affichent la somme des nombres reçus. Indications: le signal SIGUSR1 sera envoyé pour demander au fils de lire dans le tube; le signal SIGUSR2 permettra de demander au fils d'afficher la somme et de se terminer. 2.8.Correction de codes sources a) Le fichier err1.c contient un code qui devrait créer un second processus pour que le fils déclenche après 5 secondes une alarme qui provoque l'affichage d'un message et l'envoie du signal SIGUSR1 au père. Lorsque le père reçoit le signal, il doit afficher un message et s'achever. Cependant, le programme rend rapidement la main sans afficher aucun message.
Travaux pratique d'informatique Temps Réel,, TP 2 5 Corrigez l'erreur. b)le fichier err2.c contient un code qui devrait afficher et décrémenter la valeur, passée en argument, d'une variable de 0,1 tous les dixièmes de secondes. Le programme s'arrêterait lorsque la variable arrive à 0. Cependant, le programme n'affiche rien et ne s'arrête pas. Corrigez l'erreur. c) Le fichier err3.c contient un code qui devrait créer un second processus pour que le fils demande en boucle de valider l'extinction du père. Lorsque l'utilisateur valide, le fils envoie un signal au père pour que celui ci «tue» le fils et s'achève. Cependant, le programme ne s'achève pas. Corrigez l'erreur.
Travaux pratique d'informatique Temps Réel,, TP 2 6 2.9.Annexes Noms de signaux Description Particularités SIGABRT SIGALRM Abandon Alarme SIGFPE Erreur arithmétique Synchrone SIGHUP SIGILL SIGINT Terminaison du processus leader de session ou déconnexion modem Instruction illégale Interruption du terminal SIGKILL Signal de terminaison Non déroutable SIGPIPE SIGQUIT Écriture sur un tube sans lecteur Terminaison du terminal SIGSEGV Adressage mémoire invalide Synchrone SIGTERM SIGUSR1 SIGUSR2 SIGCHLD SIGCONT Terminaison Signal 1 défini par l utilisateur Signal 2 défini par l utilisateur Signal envoyé aux processus dont un fils s'est terminé Reprise du processus SIGSTOP Suspension du processus Non déroutable SIGTSTP SIGTTIN SIGTTOU Émission vers le terminal du caractère de terminaison Écriture sur le terminal pour un processus d arrière plan Lecture du terminal pour un processus d arrière plan 2.10.Références bibliographiques 1. Informatique temps réel Pierre Manneback, 2006 2007 Faculté Polytechnique de Mons. 2. Informatique de base Notes de Laboratoire 2005 2006 Mohammed Benjelloun Faculté Polytechnique de Mons. 3. Programmation Linux Neil Matthew, Richard Stones Eyrolles 2000. 4. Programmation système en C sous Linux Christophe Blaess Éditions Eyrolles, 2005.