Skip to content

MOOC NSI - fondamentaux.

Transcription de la vidéo

4.2.2.9 part2 : Flux d'entrée-sortie II

[00:00:01]

Voyons maintenant ce que sont les tubes et comment on peut les utiliser pour faire communiquer les processus. Nous avons vu dans le préambule sur les flots d'entrées/ sorties, que les flux d'entrées/sorties peuvent être dirigés soit vers des fichiers, soit vers les organes d'entrées/sorties. Grâce aux tubes, il est possible de diriger la sortie d'un processus vers l'entrée d'un autre processus et c'est ainsi que les tubes permettent à deux processus de communiquer. En effet, un tube est un fichier anonyme qui sert de tampon. Ici sur le schéma nous avons deux processus P1 et P2. La sortie de P1 est redirigée vers le tube, donc tout ce que P1 va produire comme sortie va être écrit dans le tube alors que le processus P2 a son entrée dirigée depuis le tube, donc tout ce que li P2 va venir du contenu du tube. Nous parlons de producteur-consommateur puisque P1 produit des données et P2 consomme les données et les traite.

Regardons un exemple d'une commande qu'on peut taper en ligne de commande. Donc, ici, nous tapons cat *.c pipe (qui est le mot anglais pour tube) grep var. Rappelez vous, le shell va se charger de l'exécution de cette commande. Ce qu'il va faire, c'est qu'il va voir qu'il y a deux commandes et le symbole qui correspond au pipe. Il va donc créer deux processus, un par commande. Il va aussi se charger de la création du pipe et de la redirection des entrées/sorties. Le premier processus va exécuter la première commande cta *.c . Il va donc avoir comme sortie l'affichage du contenu de tous les fichiers .c qui sont dans le répertoire courant.

[00:02:07]

Ce contenu, il va être passé en entrée au deuxième processus qui lui va rechercher la chaîne var. Comme résultat de cette commande globale nous allons avoir toutes les lignes des fichiers .c qui contiennent la chaîne de caractères var.

Regardons un autre exemple un peu plus compliqué. Ici, nous avons trois commandes et deux tubes. La première commande “cat f1 f2 f3” va avoir comme sortie le contenu de ces trois fichiers. La deuxième commande “grep toto” sert à chercher la chaîne de caractères toto. wc -l, la troisième commande, vient de wc (word count), mais avec le paramètre -l au lieu de compter les mots, cette commande compte les lignes. Nous allons donc avoir la création de trois processus et de deux tubes. La sortie du premier processus va être redirigée vers le deuxième processus. La sortie du deuxième processus va être redirigée en entrée du troisième processus et le troisième processus va voir sa sortie redirigée vers le fichier result. Qu'est ce qu'on va avoir comme résultat à la fin? Nous allons avoir, dans result, le nombre de lignes qui contiennent toto, qui viennent des fichiers f1, f2 et f3.

Les tubes et de manière générale, les flots d'entrées/sorties peuvent être manipulées à l'aide de l'interface système, via donc des appels système. Ici, nous allons regarder la primitive pipe qui permet de créer un tube. En Python, cette primitive est dans le module os et quand on l'invoque, on récupère deux descripteurs qui correspondent aux deux bouts du tube : le bout pour y écrire et le bout pour y lire.

[00:04:26]

Alors, si un processus utilise cette primitive et crée un tube. Il va récupérer les deux descripteurs r pour pouvoir faire la lecture et w pour pouvoir faire des écritures dans sa table de descripteurs. Si ce processus crée un fils, le fils va recopier les descripteurs puisque le fils est une copie conforme du père. Toutefois, à l'aide de ces descripteurs, le fils va pouvoir manipuler le même tube. Donc, à partir de ce moment le père et le fils partagent la ressource commune qu'est le tube. Donc, ils pourront communiquer.

Alors, le tube est un moyen de communication unidirectionnelle. Il faut un processus qui écrit et un autre qui lit. C'est pour cela qu'il faudrait que le père et le fils ferment les descripteurs qu’ils ne vont pas utiliser. Si nous décidons que le père va écrire et le fils va lire, alors le père devra fermer le descripteur r et le fils devra fermer le distributeur w.

Voyons comment on utilise ceci dans le programme Python suivant. Sur ce transparent vous voyez la partie fixe, sur le transparent suivant, vous allez voir la partie père. Alors nous avons le père qui est lancé en premier et qui va exécuter la fonction main. C'est lui qui crée le tube en invoquant la fonction pipe et il récupère les deux descripteurs r et w. Par curiosité, nous allons imprimer les valeurs de ces deux descripteurs. Ensuite, le père fait fork et donc crée un fils. Si tout se passe bien, le fork arrive à créer le processus file et renvoie 0 au fils.

[00:06:35]

Donc, le fils va exécuter les instructions qui sont montrées ici. Tout d'abord, comme on a dit, il ferme le bout du tube qu'il ne va pas utiliser. Donc, il ferme le w puisqu'il ne va pas écrire dans le tube. Ensuite, ici, nous avons une instruction qui nous permet de passer de descripteurs fichiers à un objet fichier dans Python qui nous permet d'utiliser des primitives de plus haut niveau et plus faciles à manipuler. Donc, dans ce programme, on va tout simplement utiliser des primitives read et write avec des objets fichiers et nous n'allons pas nous préoccuper de l'encodage de données ou de la taille des données qui transitent entre les deux processus. Donc, le fils peut tout simplement faire rio.read et afficher ce qu'il a lu. Ensuite, il va fermer le fichier tube et il va se terminer. Du côté père c'est quasi symétrique. Il va donc fermer le bout du tube qui correspond à la lecture. Il va également récupérer un objet fichier pour pouvoir le manipuler plus facilement et il va pouvoir utiliser la primitive write et va donc écrire “Bonjour fiston! “ Après ça, il ferme le tube, il va attendre la terminaison du fils et va se terminer à son tour.

Regardons donc une trace d'exécution de ce programme. Je lance donc ce programme, pipe.py, et la première chose que je vois, c'est donc les valeurs de descripteurs qui correspondent aux deux bouts du tube. Et je vois que c’est 3 et 4.

[00:08:24]

Pourquoi 3 et 4? Parce que vous avez vu dans la séquence précédente que 0, 1 et 2 sont déjà pris. 0 c'est l'entrée standard, 1 c'est la sortie standard et 2 c'est la sortie d'erreur. Donc, dans la table, le système d'exploitation va prendre les premières entrées qui sont libres, ici 3 et 4. Nous voyons l'affichage du père qui écrit. On voit bien que le fils lit. “Bonjour fiston”. Le fils se termine et ensuite le père se termine après avoir attendu la terminaison du fils.

Si on n'utilise pas des primitives de haut niveau, qui manipule des objets fichiers, il faut utiliser les primitives de base du système read et write, qui travaillent avec les descripteurs de fichier. Toutefois, comme s'est montré dans ce programme qui fait la même chose que le programme que nous venons de voir, du côté du père, quand on fait le write, il faudrait se préoccuper de l'encodage des données, alors que du côté du fils, il faudrait, bien sûr, se préoccuper du décodage des données, mais aussi il faudrait fournir comme argument le nombre d'octets que nous voudrions lire. Donc, les lectures et les écritures sont plus sensibles à la manipulation et donc l'utilisation de fonctions de plus haut niveau facilite grandement le travail avec les tubes.

Pour résumer, nous avons fait connaissance avec les tubes qui sont un moyen de communication entre processus. Ce sont des fichiers spéciaux qui permettent de diriger la sortie d'un processus vers l'entrée d'un autre processus. Ils peuvent être manipulés en ligne de commande et via l'interface programmatique du système.