BCI INF Module ARSE

TP Fichiers

B. Dupouy et S. Gadret

SOMMAIRE

Ce qu'il faut faire :

1ère partie    Rendre les exercices : I.2, I.3 et I.4

2ème partie Rendre l'exercice : II.4.

Envoyer les exercices par mail à l'enseignant
qui vous fait les cours.
Le texte du sujet ( subject ) doit contenir le nom de l'UE et celui du groupe (par exemple : INF104-G3).
mail -s"G3" leprof < exo.c

  1. LES FICHIERS ORDINAIRES
    1. Partage de fichiers (accès en coopération).
    2. Synchroniser les accès à un fichier partagé
    3. "Time-out" dans la section critique et point de reprise
    4. Traitement de l'interblocage
  2. LES TUBES
    1. Utilisation en Shell
    2. Utilisation en C
    3. Exemple
    4. Exercice

I. Les fichiers ordinaires

I.1 Partage de fichiers (accès en coopération).

On rappelle que tous les fichiers ouverts par un processus sont vus par l'ensemble de ses descendants. Unix n'impose aucune contrainte d'accès, toutes les synchronisations sont à la charge de l'utilisateur. L'exemple suivant illustre l'accès simultané, par deux processus, à un même fichier en lecture puis à un même fichier en écriture.

Exemple :

Le programme exo1.c fait faire la copie d'un fichier (fichier source ) dans un autre (fichier destination) par deux processus. Les noms des deux fichiers, source et destination, sont passés sur la ligne de commande (utilisation de argc, argv).
On testera ce programme avec le fichier sema.txt, (exo1 est le nom de l'exécutable correspondant à exo1.c) en faisant :

puis on consultera sortie.txt avec more ou un éditeur pour constater (et expliquer) les déséquencements de caractères.
Utiliser éventuellement la commande diff pour trouver rapidement où sont les différences entre les deux fichiers :

diff sema.txt sortie.txt

ou

diff exo1.c sortie.txt


Question :
Comment pourrait-on résoudre ce problème ?

I.2 Synchroniser les accès à un fichier partagé

Après l'avoir lu et recopié, compiler le programme suivant.

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.

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)  :

a.out /dev/pts/3

  1. 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.
  2. Modifier le code en utilsant lockf pour assurer l'accès en exclusion mutuelle à la section critique, empéchant ainsi l'accès simultané en écriture à la ressourcee partagée.
  3. 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.
  4. 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, empechant ainsi l'autre d'avoir accès à la ressource.
    Pour remédier (de manière peu élégante) à ce problème, faire un appel à sleep après le déverrouillage.

I.3 "Time-out" dans la section critique et point de reprise

Aller maintenant chercher un autre fichier.
Le canevas contenu dans ce fichier propose une version améliorée du programme précédent.

Fonctionnement de cette nouvelle version :

  1. On ne doit pas rester plus de MAX secondes dans la section critique.
  2. 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 P (la demande de prise du verrou).
Comment faire ?
  1. Utiliser la fonction alarm(t) qui émet le signal SIGALRM au bout de t secondes.
  2. 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.
  3. 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.4 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 :

Dans cet exercice on va ouvrir deux fichiers et poser 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)

A faire :

Récupérer le programme se trouvant dans le fichier lockf_dead1V0.c.

  1. Compiler ce programme (gcc -Wall lockf_dead1V0.c -o lock_dead),
  2. Lancer l'exécutable correspondant dans deux fenêtres différentes,
  3. 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 :
    lock_dead /dev/pts/6 /dev/pts/8/
    Exécution sur fenêtre 2 :
    lock_dead /dev/pts/8 /dev/pts/6/

  4. Modifier le programme en modifiant comme indiqué les lignes notées :
    /* ... */
  5. Exécuter à nouveau. On doit obtenir des traces du type :
    Pid 3652 : entree dans SC1 (/dev/pts/4), Ret_lockf=0
    Pid 3652 : entree dans SC (/dev/pts/1), Ret_lockf=0, Entrees =1
    Pid 3652 : sortie de SC2 (/dev/pts/1), Ret_lockf=0
    Pid 3652 : sortie de SC1 (/dev/pts/4), Ret_lockf=0
    Pid 3652 : entree dans SC1 (/dev/pts/4), Ret_lockf=0
    lockf Fichier_2: Deadlock situation detected/avoided
    Pid 3652 : ulock /dev/pts/4 (Deadlocking=0)
    Pid 3652 : entree dans SC1 (/dev/pts/4), Ret_lockf=0
    lockf Fichier_2: Deadlock situation detected/avoided
    Pid 3652 : ulock /dev/pts/4 (Deadlocking=1)
    Pid 3652 : entree dans SC1 (/dev/pts/4), Ret_lockf=0
    Pid 3652 : entree dans SC (/dev/pts/1), Ret_lockf=0, Entrees =2
    

II. Les tubes

Les pipes, en français tubes, sont l'implantation Unix des tampons gérés 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). Le tampon est visualisé ci-dessous :

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 :

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

Remarque :
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

D'après l'exemple donné ci-dessus et les TP signaux et processus, écrire un programme dont le fonctionnement est le suivant :

  1. création d'un fichier tube,
  2. 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(),
  3. 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 !
Schéma de la communication :

Le canevas du programme à écrire est là.

©(Copyright) dupouy@inf.enst.fr