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