Aller au contenu principal

Systèmes d'exploitation — fondations · L2 · Section 6/12

Tubes nommés

Progression

Points d’expérience : XPSérie de jours consécutifs : · —Progression du module : — / —compris

#Les tubes nommés

Prérequis

  • Chapitre Fichiers: open, read, write, blocage sur lecture.
  • Shell: redirections et arrière-plan.

Objectifs d'apprentissage

  • Créer une FIFO et expliquer pourquoi l'ouverture bloque jusqu'à ce que l'autre bout existe.
  • Prédire le comportement d'un lecteur quand tous les écrivains disparaissent.
  • Connaître la garantie d'atomicité de PIPE_BUF et ses limites.

Les tubes sont souvent la première porte vers la communication inter-processus. Un tube anonyme naît d'un appel pipe et vit tant que les deux extrémités demeurent ouvertes; il relie uniquement des processus apparentés. Un tube nommé (FIFO) enfonce le clou: mkfifo crée une entrée spéciale dans le système de fichiers, accessible à tout processus respectant les permissions, même sans lien de parenté.

#0. Le tube du shell, avant celui du programmeur

Avant d'écrire une ligne de C, on rencontre les tubes dans le shell. Le cours d'Unix les présente comme une simplification des redirections: pour afficher page par page le contenu d'un répertoire, on écrit d'abord

bashbash

1ls -l /usr/bin > resultats ; more resultats ; rm resultats

resultats est un fichier auxiliaire, puis on s'aperçoit qu'Unix offre un mécanisme dédié, le tube, qui connecte directement la sortie standard d'un processus à l'entrée standard d'un autre sans fichier temporaire:

bashbash

1ls -l /usr/bin | more

Deux propriétés distinguent le tube de l'enchaînement séquentiel. Les processus sont exécutés simultanément, et c'est le système qui gère leur synchronisation: un producteur plus rapide que son consommateur est suspendu temporairement. Un programme qui transforme son entrée standard en sortie standard s'appelle un filtre; c'est ce contrat minimal qui rend les tubes composables à volonté.

#1. Création et ouverture

mkfifo chemin mode produit un fichier FIFO. (En C: mkfifo("flux", 0666) puis open de part et d'autre.) Lorsqu'un processus l'ouvre en lecture, il est bloqué tant qu'aucun écrivain n'a ouvert l'autre extrémité, et inversement. Cette synchronisation implicite évite de perdre les premiers octets. Contrairement à un pipe anonyme, une FIFO peut être ouverte par plusieurs écrivains ou lecteurs indépendants, pour peu que chacun respecte la convention (par exemple écrire des messages terminés par un saut de ligne).

Expérience du blocage, dans deux terminaux:

bashbash

1# Terminal 12cat flux        # semble ne rien faire: aucun écrivain3# Terminal 24echo bonjour > flux5# Terminal 1 affiche alors bonjour

Et l'échec fragile, toujours dans deux terminaux: ouvrez la FIFO en écriture seule alors qu'aucun lecteur n'existe (echo x > flux avec le lecteur absent): l'écriture reçoit SIGPIPE, et le shell affiche « Broken pipe ». C'est la même mécanique qui explique un grep tué en amont d'un pipe quand le consommateur en aval a déjà quitté.

#2. Atomicité et limitations

Le noyau garantit que toute écriture inférieure ou égale à PIPE_BUF octets (au moins 4 096 sur Linux, valeur obtenue par pathconf ou getconf PIPE_BUF) reste atomique: elle ne sera pas entrelacée avec celle d'un autre écrivain. Au-delà, les messages peuvent se mélanger. Par conséquent, un protocole sérieux doit segmenter ses données ou ajouter une couche de sérialisation (longueur + contenu), surtout si plusieurs producteurs écrivent simultanément.

Autre loi à connaître: une FIFO ne conserve pas de données lorsque plus personne ne la tient ouverte. Si un lecteur arrive à retardement, il ne verra que les messages futurs. La FIFO est un canal, pas une file persistante: pour stocker, il faut un fichier; pour dialoguer, une FIFO.

#3. Exploration pratique

bashbash

1mkfifo flux2producer() { for i in $(seq 1 5); do echo "tick $i"; sleep 1; done }3consumer() { while read -r line; do echo "[ reçu ] $line"; done }4producer > flux &5consumer < flux

Observation attendue: les lignes « tick 1 » à « tick 5 » s'affichent une par une, à la seconde: la lecture est synchronisée sur l'écriture, sans polling. Quand le producteur se termine, read renvoie faux et le consommateur sort proprement de sa boucle. Les outils système révèlent l'objet: ls -l flux affiche le type p en première colonne, stat confirme un fichier FIFO.

Nettoyage comme tout fichier: rm flux (et le recréer au démarrage suivant; ne jamais supprimer l'ancien tant qu'une instance tourne encore).

#Atelier

Construisez une mini-chaîne de surveillance à trois composants: un collecteur écrit des mesures (charge, horodatage) dans une FIFO metriques.fifo, un lecteur les consomme et déclenche une alerte quand un seuil est franchi, et un journaliseur enregistre chaque ligne dans un fichier.

Variante d'expérience 1: lancez deux producteurs simultanés écrivant des lignes courtes; grâce à l'atomicité sous PIPE_BUF, chaque ligne reste intacte.

Variante d'expérience 2: faites écrire à l'un des producteurs des blocs très supérieurs à PIPE_BUF sans discipline de cadrage: des lignes entremêlées apparaissent, démonstration de la frontière du mécanisme. La solution (préfixer chaque message par sa longueur, ou borner chaque écriture) est à retenir pour tout protocole maison.

Terminez en supprimant la FIFO et en validant que le programme sait la recréer proprement au démarrage suivant.

#4. Un serveur FIFO, du côté du cours

L'archive de scripts fournie avec l'UE contient un couple serveur/client qui met en œuvre un annuaire de noms à travers une FIFO, et qui vaut comme modèle de protocole minimal. Le serveur expose une FIFO publique ~/noms_ident.fifo; chaque client crée sa propre FIFO de réponse, dont le nom est unique, et en communique le nom au serveur.

bashbash

1# fifo_client.sh — extrait2FIFO_SRV=~/noms_ident.fifo3FIFO_CLT=~/fifo_$$.fifo4 5if [ ! -p $FIFO_SRV ] ; then6	echo "Le serveur n'est pas accessible"7	exit 18fi9 10mkfifo -m 0622 $FIFO_CLT11echo "$1 $FIFO_CLT" > $FIFO_SRV12cat < $FIFO_CLT13rm -f $FIFO_CLT

Quatre techniques sont à retenir de cet extrait. Le test [ ! -p ... ] vérifie que le chemin est bien un tube nommé: c'est le type p que test sait distinguer. mkfifo -m 0622 fixe les permissions à la création, comme mkdir -m le fait pour un répertoire. Le nom de la FIFO de réponse embarque le PID du client (fifo_$$.fifo), ce qui garantit l'unicité sans coordination. Enfin, le client communique deux champs sur une seule ligne, $1 et $FIFO_CLT, que le serveur relira avec read IDENT FIFO_CLT.

bashbash

1# fifo_serveur.sh — extrait2FIFO_SRV=~/noms_ident.fifo3 4function gestionnaire_signaux5{	6	rm -f $FIFO_SRV7	exit 0;8}9trap gestionnaire_signaux EXIT QUIT INT HUP10 11if [ -e $FIFO_SRV ] ; then12	echo "FIN" > $FIFO_SRV &13	exit 0;14fi

Côté serveur, trois mécanismes se répondent. Le trap sur EXIT QUIT INT HUP garantit que la FIFO publique est supprimée quel que soit le chemin de sortie: c'est la contrepartie exacte du « ne jamais laisser traîner une FIFO » recommandé plus haut. La boucle while [ ! $FIN ] ; do read IDENT FIFO_CLT < $FIFO_SRV ; done rouvre la FIFO à chaque tour par la redirection placée sur read: la lecture bloque jusqu'à ce qu'un client écrive, puis la boucle repart. Enfin, la ligne read IDENT FIFO_CLT décompose une ligne en deux champs sur les blancs — IFS joue ici le rôle du délimiteur.

#Exercices

1. Qu'est-ce qu'un filtre? En quoi le tube diffère-t-il d'un enchaînement séquentiel par ;?

2. Écrire la ligne de commandes qui affiche page par page le contenu de /usr/bin sans créer de fichier temporaire. Quelle écriture utilise un fichier auxiliaire, et en quoi est-elle moins bonne?

3. Comment vérifier qu'un chemin est bien un tube nommé? Comment créer une FIFO avec des permissions imposées?

4. Pourquoi le nom de la FIFO de réponse d'un client embarque-t-il son PID?

5. Que fait un trap sur EXIT dans un serveur à FIFO, et pourquoi est-ce nécessaire?

Corrections détaillées

1. Un filtre est un programme qui effectue une transformation sur son entrée standard pour produire sa sortie standard; il s'utilise typiquement dans un tube. Le tube | connecte la sortie standard d'un processus à l'entrée standard du suivant sans fichier auxiliaire, et les processus sont exécutés simultanément, le système gérant leur synchronisation — un producteur plus rapide que son consommateur est suspendu. Avec ;, les commandes s'exécutent l'une après l'autre, sans rapport entre elles.

2.

bashbash

1ls -l /usr/bin | more

L'écriture avec fichier auxiliaire est ls -l /usr/bin > resultats ; more resultats ; rm resultats: elle crée un fichier temporaire, ce qui consomme de la place disque, impose un nettoyage, et séquentialise les deux commandes — il faut attendre la fin du listage complet avant de pouvoir lire la première page. Le tube, lui, permet à more d'afficher la première page pendant que ls produit encore.

3. Avec le prédicat -p de test, qui est vrai si le chemin est un tube nommé:

bashbash

1[ -p flux ] && echo "c'est une FIFO"2mkfifo -m 0622 ~/fifo_$$.fifo

mkfifo -m mode fixe les permissions à la création, sur le même modèle que mkdir -m. L'alternative en deux temps est mkfifo chemin suivi de chmod.

4. Pour garantir l'unicité du nom sans coordination centrale: chaque client a un PID différent, donc fifo_$$.fifo désigne une FIFO distincte pour chacun. Si tous les clients partageaient le même nom, leurs réponses s'entremêleraient et le serveur ne saurait plus à qui il répond. Le PID joue ici le rôle d'un identifiant de session.

5. Le trap gestionnaire_signaux EXIT QUIT INT HUP exécute rm -f $FIFO_SRV avant que le serveur ne se termine, quel que soit le chemin de sortie — fin normale, interruption au clavier, ou déconnexion du terminal. C'est nécessaire parce qu'une FIFO est un objet du système de fichiers: si le serveur meurt en laissant le fichier derrière lui, le prochain démarrage trouvera un tube que personne n'écoute, et un client qui y écrit restera bloqué en attente d'un lecteur. La suppression à la sortie garantit que la FIFO présente sur le disque correspond toujours à un serveur vivant.