I. Fichiers partagés, gestion classique
Il vous est demandé si possible de rendre vos codes dans une seule archive
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. L’idée est d’aller écrire depuis un terminal x sur un terminal x’ : ici ce sera x et y qui écriront vers z.
- 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. (vous devriez pouvoir obtenir un texte mélangé …)
- 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 repositionner 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. Utilisez la fonction sleep pour remédier à ce problème.
Ce code est à rendre
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 (appels systèmes requis alarm, signal, sigsetjmp et siglongjmp) :
- On ne doit pas rester plus de t_max secondes dans la section critique (ce qui est différent d’attendre l’entrée dans la section critique). A vous de fixer la valeur de t_max (via un argument passé en ligne de commande dont vous ajouterez la récupération).
- 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 qui test et réalise dès que possible le verrouillage du fichier.
Instructions:
- Utiliser la fonction alarm(t) qui émet le signal SIGALRM au bout de t secondes juste après être effectivement rentré dans la section critique. A la sortie de la section critique, vous désactiverez l’alarme (cf page de manuel de alarm, ou exécutez : man 3 alarm)
- Si le signal est émis et reçu par le processus, on souhaite exécuter une fonction de traitement du signal qui rend le verrou pris mais conservée trop long temps puis déclenche le renvoi 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.
- Il faut s’assurer d’avoir effectivement sauvegardé l’état d’exécution pour pouvoir faire le renvoi correctement. De plus, le placement de la fonction permettant cette sauvegarde doit garantir que l’exclusion mutuelle ne sera pas mise en défaut après reprise.
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. Vous pouvez aussi avoir un message d’erreur si le fichier n’existe pas ou que vous ne possédez pas les permissions adéquates.
Dans cet exercice on va ouvrir deux fichiers et en verrouiller l’accès de façon à provoquer une situation d’interblocage. Ceci peut être obtenu en exécutant le pseudo code suivant :
Processus 1 | Processus 2 |
vérrouillage f1 vérrouillage f2 section critique dévérrouillage f2 dévérrouillage f1 |
vérrouillage f2 vérrouillage f1 section critique dévérrouillage f1 dévérrouillage f2 |
Instructions :
Un canevas de programme vous est fourni dans le fichier source TP_SELC/TP14/material/file_deadlock.c
Ce code ne contient pas les synchronisations décrite dans le tableau ci-dessus. Vous pouvez le constater en compilant puis exécutant le programme en passant deux noms de fichiers comme paramètre (vous pouvez suivre l’indication au point 3 pour lancer ce programme).
- Modifiez le code en ajoutant la synchronisation sur le premier fichier seulement. Puis vérifiez qu’il n’y a plus data race pour ce fichier (correspondant à file1), ni d’interblocage. (rendez ce code, il contribue à la note de 2 )
- Complétez le code pour implémenter le schéma décrit ci dessus dans le tableau aux positions suggérées dans le code. (rendez ce code, il contribue à la note de 2).
- Exécutez ce code en utilisant log1 et log2 comme fichier de sortie ; placé vous dans le même répertoire dans deux terminaux différents et exécutez :
Exécution sur fenêtre 1 : ./deadlock log1
Exécution sur fenêtre 2 : ./deadlock log2
On doit obtenir une trace du type (où —— est un chemin de fichier):
Pid 3313 : entered critical section 1 (-------), lockf_ret=0 lockf file_2: Resource deadlock avoided Pid 3313 : ulock ------ (deadlock_nb=0) Pid 3313 : entered critical section 1 (-------), lockf_ret=0 lockf file_2: Resource deadlock avoided Pid 3313 : ulock ------ (deadlock_nb=1) Pid 3313 : entered critical section 1 (-------), lockf_ret=0 lockf file_2: Resource deadlock avoided Pid 3313 : ulock ------- (deadlock_nb=2)
II. Les tubes
Les tubes, (pipes en anglais), sont l’implantation Unix d’informations partagées suivant le schéma producteur/consommateur. Ces tubes sont donc constitué d’une zone mémoire partagée qui s’utilise comme une file et qui est protégée contre le data race. (Le
Prod_put(T_d d) { P(S1) write(addr_element(i), d) i = next_prod(i) V(S2) } |
T_d Cons_get() { T_d aux; P(S2) aux=read(addr_element(i)) i = next_cons (i) V(S1) return aux; } |
Vous n’avez donc pas à craindre de « data race » mais seulement à correctement initialiser le service et l’utiliser.
II.1 Utilisation avec le 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 processus dont la ligne de description contient le texte « dupont » ici probablement un nom d’utilisateur. | ps -axl | grep "dupont" |
Retrouver tous fichiers qui ont pour permissions au moins une catégorie de permission (owner, group ou other) rwx |
ls -l | grep "rwx" |
Remarque : le ‘|’ n’est pas à confondre avec cet usage ps `pidof hello` qui permet d’exécuter pidof avec paramètre hello puis d’en utiliser le résultat comme paramètre à ps (i.e. cela ne va pas sur l’entrée standard, et pidof termine son exécution avant que ps ne commence la sienne).
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
En général, on sort de la boucle de lecture lorsque tous les producteurs (ceux qui font write) ont fait close, sinon on continue à boucler sur read. Ici, il n’y a qu’un seul producteur. On constatera que le tube n’est pas immédiatement fermé lorsqu’il reçoit EOF, qui peut être déclenché sur l’entrée standard 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
Rendez ce code (il contribue à la note de 2)
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 );