Communication par Signal - premiers pas
Transcription
Communication par Signal - premiers pas
Licence Informatique 2006-2007 Système d’exploitation Communication par Signal - premiers pas Quelques remarques sur la gestion des signaux UNIX ================================================== Un premier problème se pose lorsque plusieurs exemplaires d’un même signal sont reçus par un processus dans un lap de temps très court, typiquement le temps nécessaire à l’execution de la routine associée au signal. Une premiere question est : mémorise-t-on l’arrivée des différents signaux et si oui comment ? La réponse pour les signaux "classiques" UNIX est : il existe un masque, avec un bit par signal, qui dit s’il existe une occurrence du signal en attente (car un signal n’arrive pas forcement alors que le processus est en execution et dans ce cas il faut bien pouvoir mémoriser le signal afin de démarrer le processus sur la routine associée au signal lorsqu’il sera a nouveau élu par l’ordonnanceur, sinon cela signifierait que le comportement d’un processus, et donc le résultat de l’execution d’un programme, depend de l’ordonnancement, ce qui serait fâcheux !). Un bit par signal cela signifie 0 ou 1, autrement dit on peut savoir s’il existe 1 signal en attente (pending signal en anglais) ou pas. Consequence : si plusieurs signaux arrivent en même temps un seul sera mémorise ! Il existe 2 solutions pour contourner cette limitation : arranger le programme pour éviter qu’une telle situation ne se produise ou utiliser les signaux temps reel POSIX1b. Dans le cas des signaux temps reel un mot de 32 bits est associé à chaque signal, qui donne le nombre d’occurrences du signal en attente. Une seconde question est : que se passe-t-il si un signal arrive alors que le processus est deja en train d’exécuter la routine associée a un signal (éventuellement le même). Logiquement la prise en compte des signaux est immediate (principe des interruptions), ce qui signifie que l’execution de la routine est interrompue afin de permettre l’execution de la routine associée au signal le plus recent. Ce comportement peut avoir des consequences ennuyeuses. Imaginez le handler suivant : void routine (int n) { static int i=0 ; if (i == 0) i++ ; else i– ; } Cette routine implémente une bascule 0/1. Imaginons maintenant qu’elle soit associée a un signal et que 2 occurrences du signal soient reçues presque au même instant par le processus. Le second signal est reçu alors que i vaut 0 et que l’execution de la routine vient d’effectuer le test (i == 0), qui par consequent, aiguille le traitement sur l’instruction i++. L’execution de la routine est alors interrompue et la même routine est a nouveau exécutée en réponse a la reception du second signal. Puisque i vaut toujours 0 le test (i == 0) renvoie vrai et i est incrémente (i vaut désormais 1). Puis on reprend l’execution de la routine interrompue qui en est a l’instruction i++, ce qui a pour consequence de faire passer la valeur de i a 2. Ainsi on se retrouve avec une valeur de i qui est théoriquement impossible par programmation ! Que faudrait-il faire afin d’éviter une telle situation ? Garantir l’execution atomique de la routine, ce qui revient a bloquer les signaux pendant l’execution d’un handler. Le blocage des signaux est une operation courante, qui signifie que la prise en compte du signal est différée tant que le signal est bloque, autrement dit il n’y a pas de perte de signaux dus au blocage, ils sont pris en compte des le déblocage. La fonction signal, si elle a pour avantage sa simplicité d’utilisation, a l’inconvenient de ne pas permettre le blocage des signaux. La norme POSIX1, qui définit le comportement de l’API C pour la programmation système, stipule que les bibliothèques de gestion de signaux doivent permettre le blocage des signaux et même que ce blocage doit être effectue implicitement pendant l’execution d’un handler. Les fonctions Université de Pau et des Pays de l’Adour Licence Informatique 2006-2007 Système d’exploitation de la bibliothèque POSIX1 sont les primitives sigaction(), qui remplace signal(), et sigprocmask(), qui permet le blocage ou le déblocage de signaux a n’importe quel moment. A noter que la norme POSIX precise également que les routines associées aux signaux le restent après prise en compte d’un signal. La primitive signal() ne faisant pas partie d’une normalisation, le comportement est laisse au choix du constructeur, ce qui peut rendre les applications non portables. Ainsi sous Linux la fonction signal ne restitue pas les handlers par défaut alors que sous Systeme V (Sun Solaris par exemple) c’est le cas et la premiere instruction du handler est alors typiquement un appel a signal() afin d’associer a nouveau le handler au signal. Exercice 1 : Déroutement d’un signal Écrire un programme qui crée un fils. 3 Le fils déroute les signaux SIGTERM et SIGKILL, puis s’endort. 3 Le pere lui envoie successivement ces deux signaux. Que constatez-vous ? 1 2 3 4 #include<stdio.h> #include<signal.h> #include<unistd.h> #include<errno.h> 5 6 7 8 9 10 11 void Handler(int sig) { struct sigaction action, actionant; printf("<%d> Signal recu: %d\n", getpid(), sig); sleep(10); } 12 13 14 15 16 17 18 int main() { pid_t pid, ppid; int retour; struct sigaction action; 19 20 21 22 system("clear"); printf("\t COMMUNICATION ENTRE PROCESSUS PAR SIGNAUX\n"); printf("\t *****************************************\n"); 23 24 25 26 27 28 29 30 31 32 33 34 35 36 pid = fork(); switch(pid) { case -1: perror("Fork"); exit(-1); case 0: action.sa_handler = Handler; if ((retour= sigaction(SIGTERM,&action,NULL)) != 0) { perror("sigaction SIGTERM"); } printf("Le fils a deroute SIGTERM.\n"); Université de Pau et des Pays de l’Adour Licence Informatique 2006-2007 Système d’exploitation sigaction(SIGKILL,&action,NULL); if ((retour= sigaction(SIGKILL,&action,NULL)) != 0) { perror("sigaction SIGKILL"); } printf("Le fils a deroute SIGKILL.\n"); sleep(20); break; 37 38 39 40 41 42 43 44 45 default: sleep(3); printf("Le pere envoie SIGTERM %d au fils\n",SIGTERM); if ((retour= kill(pid, SIGTERM)) == -1) { perror("kill SIGTERM"); exit(-1); } sleep(5); printf("Le pere envoie SIGKILL %d au fils\n",SIGKILL); if ((retour= kill(pid, SIGKILL)) == -1) { perror("kill SIGKILL"); exit(-1); } sleep(3); wait(NULL); break; 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 } 64 65 } Exercice 2 : Synchronisation par SIGSTOP et SIGCONT On désire simuler un bar électronique. Écrire un programme qui crée un fils. Le père jouera le rôle du barman et le fils celui du client. Le barman doit effectuer deux tâches servir_verre(1) et servir_verre(2). Le client doit effectuer deux tâches demander_verre(1) et demander_verre(2). On veut synchroniser la réalisation de ces tâches dans cette ordre : demander_verre(1), servir_verre(1), demander_verre(2), servir_verre(2) en utilisant les signaux SIGSTOP et SIGCONT. Comment feriez vous pour faire la même chose sans utiliser ces signaux ? 1 2 3 4 #include #include #include #include <signal.h> <unistd.h> <stdio.h> <string.h> 5 6 7 8 9 10 11 12 void servir_verre(int iNum) { printf ("Je suis %d et je sers le verre %d\n", getpid(), iNum); } void demander_verre(int iNum) { printf ("Je suis %d et je demande le verre %d\n", getpid(), iNum); Université de Pau et des Pays de l’Adour Licence Informatique 13 2006-2007 Système d’exploitation } 14 15 16 17 18 int main(int argc, char *argv[]) { int pid, statut; char *p; 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 pid = fork(); switch(pid) { case -1: { perror("Fork"); exit(-1); } case 0 : demander_verre(1); sleep(4); kill(getppid(),SIGCONT); kill(getpid(),SIGSTOP); demander_verre(2); sleep(4); kill(getppid(),SIGCONT); break; default : kill(getpid(),SIGSTOP); servir_verre(1); sleep(4); kill(pid,SIGCONT); kill(getpid(),SIGSTOP); servir_verre(2); wait(&statut); } } 47 Exercice 3 : Le cas particulier de SIGALRM Écrire un programme qui attend que l’utilisateur réagisse à un message de type message d’alerte. Il a 5 secondes pour le faire. Si au bout de trois tentatives l’utilisateur ne l’a toujours pas fait le programme lance le programme terminaison_automatique() sinon il lance le programme terminaison_manuelle(). On simule l’acquittement du message d’erreur par une pression sur le clavier. 1 2 3 #include <signal.h> #include <stdio.h> #include <errno.h> 4 5 int iSaisie, iCpt; 6 7 8 void f(int sig) { Université de Pau et des Pays de l’Adour Licence Informatique Système d’exploitation printf("TOP\n"); iSaisie = 1; iCpt++; 9 10 11 12 2006-2007 } 13 14 15 16 17 18 19 20 21 void TerminaisonManuelle() { printf("ARRET MANUEL\n"); } void TerminaisonAutomatique() { printf("TANT PIS - ARRET AUTOMATIQUE\n"); } 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 int main() { struct sigaction action; action.sa_handler = f; if(sigaction(SIGALRM,&action,NULL)==-1) { perror("Erreur Sigaction"); exit(-1); } iSaisie = 1; iCpt = 1; while((iSaisie) && (iCpt <= 3)) { iSaisie = 0; alarm(5); printf("ALERTE - Validez l’arrêt par une pression sur la touche ENTREE\n scanf("%*c"); if(iSaisie == 0) alarm(0); } if(iSaisie == 0) TerminaisonManuelle(); else TerminaisonAutomatique(); } 49 Exercice 4 : Le traitement des zombies avec le signal SIGCHLD Écrire un programme qui crée un fils. Le père déroute le signal SIGCHLD pour supprimer le fils zombie, puis il fork. Le fils s’endort. Le programme ne se terminera pas il faudra le tuer à partir de la ligne de commande. 1 2 3 4 #include #include #include #include <signal.h> <stdio.h> <unistd.h> <errno.h> Université de Pau et des Pays de l’Adour Licence Informatique 5 6 7 8 9 10 11 12 13 14 15 16 2006-2007 void f(int); void derouter(int); void erreur(const char *); void main() { pid_t pid; derouter(SIGCHLD); pid = fork(); switch(pid) { case -1: erreur("Erreur Fork:"); 17 18 19 20 case 0 : printf("Fils : %d\n",getpid()); break; 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 default: while(1) { printf("Je travaille\n"); sleep(3); } } } void derouter(int sig) { struct sigaction action; action.sa_handler = f; printf("signal %d recu et deroute par le pere\n",sig); if(sigaction(sig,&action,NULL)==-1) erreur("Erreur Sigaction:"); } void f(int sig) { int statut; printf("Le fils zombie est supprime %d\n", wait(&statut)); } void erreur(const char * message) { perror(message); exit(-1); } 48 Université de Pau et des Pays de l’Adour Système d’exploitation