I. Fichiers partagés, gestion classique
I.1 Synchroniser les accès à un fichier partagé
Un canevas de programme vous est donné dans le fichier source TP_SELC/TP14/material/file_mutual_exclusion.c
Fonctionnement du programme : ce programme écrit les caractères saisis au clavier sur un fichier de sortie dont on a donné le nom sur la ligne de commande au lancement du programme.
Utilisation du programme :
- On lancera le programme dans deux fenêtres différentes en lui passant à chaque fois le nom d’une même troisième fenêtre sur la ligne de commande. On rappelle que la commande tty renvoie le nom de la fenêtre dans laquelle cette commande tty a été lancée.
- Exemple de lancement du programme: pour obtenir les sorties sur la fenêtre dont le nom est /dev/pts/3, on utilisera le programme de la façon suivante (a.out est le nom que gcc donné par défaut à un exécutable, vous pouvez bien sûr lui donner un autre nom) :
$ ./a.out /dev/pts/3
- Constater que les deux processus sont ensemble dans leurs sections critiques et qu’ils ont accès simultanément en écriture à la fenêtre partagée.
- Modifier le code en utilisant la fonction lockf pour assurer l’accès en exclusion mutuelle à la section critique, empéchant ainsi l’accès simultané à la ressource partagée.
- Attention : le déverrouillage va se faire à partir de la position courante dans le fichier (cf. le man de lockf, option size). Utiliser lseek avec l’option SEEK_SET pour se repostionner au début du fichier avant d’en déverrouiller l’accès.
- Un autre problème peut survenir : celui de la famine. En effet un processus peut avoir le temps de faire le déverrouillage et de retourner prendre le verrou pendant son quantum, empêchant ainsi l’autre d’avoir accès à la ressource. Pour remédier (de manière peu élégante, nous en conviendrons) à ce problème, faire un appel à sleep après le déverrouillage.
I.2 « Time-out » dans la section critique et point de reprise
Vous devez maintenant améliorer la solution proposée précédemment pour assurer le fonctionnement décrit ci-dessous :
- On ne doit pas rester plus de MAX secondes dans la section critique. A vous de fixer la valeur de MAX (potentiellement en utilisant un nouvel argument sur la ligne de commande).
- Si cette limite est atteinte, le processus reçoit un signal qui le fait sortir de la section critique et retourner vers un point de reprise qui se trouve juste avant l’opération la demande de prise du verrou.
Instructions:
- Utiliser la fonction alarm(t) qui émet le signal SIGALRM au bout de t secondes.
- A l’arrivée du signal, exécuter une fonction de traitement du signal qui renvoie vers le point de reprise. On rappelle (cf. TP sur les signaux) qu’on peut positionner un point de reprise et y retourner grâce aux fonctions sigsetjmp et siglongjmp.
- Pour bien coder la fonction de traitement du signal, répondre à la question suivante :
dans quel état est le verrou lorsqu’on sort de la section critique sur épuisement du « time-out » ?
I.3 Traitement de l’interblocage
Lorsqu’on utilise la fonction lockf, le système peut détecter les situations d’interblocage.
Dans ce cas la fonction lockf renvoie un message d’erreur :
- sa valeur de retour est -1
- la variable errno est positionnée à EDEADLK.
Dans cet exercice on va ouvrir deux fichiers et verrouiller des verrous S1 et S2 sur ces fichiers de façon à se trouver dans une situation d’interblocage telle que :
Processus 1 | Processus 2 |
P(S1) P(S2) section critique V(S2) V(S1) |
P(S2) P(S1) section critique V(S1) V(S2) |
Instructions :
Un canevas de programme vous est fourni dans le fichier source TP_SELC/TP14/material/file_deadlock.c
- Compiler ce programme (gcc -Wall file_deadlock.c -o deadlock),
- Lancer l’exécutable correspondant dans deux fenêtres différentes,
- Constater l’accès simultané en sortie par chacun des deux processus sur les deux fichiers partagés,
par exemple (ici les deux fichiers sont /dev/pts/6 et /dev/pts/8) :Exécution sur fenêtre 1 : ./deadlock /dev/pts/6 /dev/pts/8/
Exécution sur fenêtre 2 : ./deadlock /dev/pts/8 /dev/pts/6/
- Compléter le programme en suivant les instructions dans les commentaires
/* TO BE COMPLETED */ - Exécuter à nouveau. On doit obtenir des traces du type :
Pid 3313 : entered critical section 1 (/dev/ttys000), lockf_ret=0 lockf file_2: Resource deadlock avoided Pid 3313 : ulock /dev/ttys000 (deadlock_nb=0) Pid 3313 : entered critical section 1 (/dev/ttys000), lockf_ret=0 lockf file_2: Resource deadlock avoided Pid 3313 : ulock /dev/ttys000 (deadlock_nb=1) Pid 3313 : entered critical section 1 (/dev/ttys000), lockf_ret=0 lockf file_2: Resource deadlock avoided Pid 3313 : ulock /dev/ttys000 (deadlock_nb=2) Pid 3313 : entered critical section 1 (/dev/ttys000), lockf_ret=0 lockf file_2: Resource deadlock avoided Pid 3313 : ulock /dev/ttys000 (deadlock_nb=3) Pid 3313 : entered critical section 1 (/dev/ttys000), lockf_ret=0 lockf file_2: Resource deadlock avoided Pid 3313 : ulock /dev/ttys000 (deadlock_nb=4) Pid 3313 : entered critical section 1 (/dev/ttys000), lockf_ret=0 lockf file_2: Resource deadlock avoided Pid 3313 : ulock /dev/ttys000 (deadlock_nb=5)
II. Les tubes
Les tubes, (pipes en anglais), sont l’implantation Unix d’informations partagées suivant le schéma producteur/consommateur. On en représente le principe de fonctionnement avec des sémaphores :
Producteur ... P(S1) écrire (case(i)) i = suiv_prod(i) V(S2) ... |
Consommateur ... P(S2) lire (case(i)) i = suiv_cond (i) V(S1) ... |
S1 et S2 sont initialisés ainsi : Init (S1,N) et Init (S2, 0).
II.1 Utilisation en Shell
Nous donnons ici quelques exemples très simples.
Compter le nombre d’utilisateurs sur un site | who | wc -l |
Compter le nombre de processus sur un site | ps -axl | wc -l |
Retrouver les commandes lancées par dupont |
ps -axl | grep dupont |
Retrouver tous les login contenant la chaîne « dup » dans /etc/password |
ypcat passwd | grep dup |
II.2 Utilisation en C
Pour gérer et utiliser un tube :
- on déclare un tableau d’entiers à 2 éléments :
int Tube[2]; - on créé le tube en appelant la fonction pipe() :
pipe(Tube); - on lit dans le tube en utilisant le premier élément du tableau. Par exemple, pour y lire un caractère qui doit être rangé dans la variable c (c est de type char), on fera :
read (Tube[0], &c,1); - on écrit dans le tube en utilisant le second élément du tableau, par exemple pour y écrire un caractère, on fera :
write (Tube[1], &c,1);
II.3 Exemple
Dans le programme pipe-plus.c., le père et le fils communiquent via un tube (pipe). Le père attend des caractères depuis le clavier et les écrit dans le tube. Le fils (le consommateur) se contente d’afficher ce qu’il a lu dans le tube
On sort de la boucle de lecture lorsque tous les producteurs (ceux qui font write) ont fait close, sinon on continue à boucler sur read.
On constatera que le tube n’est pas immédiatement fermé lorsqu’il reçoit EOF, qui est représenté par <CTRL D>.
II.4 Exercice sur les tubes
D’après l’exemple donné ci-dessus et les TP signaux et processus, écrire un programme dont le fonctionnement est le suivant :
- création d’un fichier tube,
- création de 4 processus fils, chacun de ces processus enverra dans le tube un message qui contient son pid, puis il fait appel à pause pour attendre un signal. Lors de l’arrivée de ce signal,faire exit(),
- attente sur le tube d’un message qui contient le pid de l’émetteur (et affichage de ce message à l’écran). Puis envoi de SIGUSR1 vers le pid en question, qui fera exit. Ne pas oublier d’acquitter cet exit !
Le canevas du programme à compléter est donné dans le fichier source TP_SELC/TP14/material/pipe_exercice.c
ATTENTION:
1 – il faut forcer l’exécution du programme sur un seul coeur de votre machine; en supposant que votre program s’appelle pipe_exec, lancez le avec la commande:
$ taskset -c 0 ./pipe_exec
2 – il y a une petite erreur dans ce fichier, ligne 65, il manque un %*s:
remplacez:
sscanf (received_msg, « %*s%*s%*s%d », &received_pid );
par:
sscanf (received_msg, « %*s%*s%*s%*s%d », &received_pid );