Comment fonctionnent les pipes sous Linux

25

J'ai lu comment les canaux sont implémentés dans le noyau Linux et je voulais valider ma compréhension. Si je me trompe, la réponse avec l'explication correcte sera sélectionnée.

  • Linux a un VFS appelé pipefs qui est monté dans le noyau (pas dans l'espace utilisateur)
  • pipefs a un seul super bloc et est monté à sa propre racine ( pipe:), à côté de/
  • les pipefs ne peuvent pas être visualisés directement contrairement à la plupart des systèmes de fichiers
  • L'entrée dans pipefs se fait via le pipe(2)syscall
  • L' pipe(2)appel système utilisé par les shells pour la tuyauterie avec l' |opérateur (ou manuellement à partir de tout autre processus) crée un nouveau fichier dans pipefs qui se comporte à peu près comme un fichier normal
  • Le fichier sur le côté gauche de l'opérateur de tuyau est stdoutredirigé vers le fichier temporaire créé dans pipefs
  • Le fichier sur le côté droit de l'opérateur de tuyau a son stdinjeu dans le fichier sur les pipefs
  • pipefs est stocké en mémoire et grâce à la magie du noyau, ne doit pas être paginé

Cette explication du fonctionnement des tuyaux (par exemple ls -la | less) est-elle à peu près correcte?

Une chose que je ne comprends pas, c'est comment quelque chose comme bash définirait un processus ' stdinou stdoutle descripteur de fichier renvoyé par pipe(2). Je n'ai encore rien trouvé à ce sujet.

Brandon Wamboldt
la source
Notez que vous parlez de deux couches de choses considérablement différentes avec le même nom. L' pipe()appel du noyau avec la machinerie qui le prend en charge ( pipefs, etc.) est de niveau beaucoup plus bas que l' |opérateur proposé dans votre shell. Ce dernier est généralement implémenté en utilisant le premier, mais ce n'est pas obligatoire.
Greg Hewgill
Oui, je fais spécifiquement référence aux opérations de niveau inférieur, en supposant que l' |opérateur appelle simplement pipe(2)comme un processus comme le fait bash.
Brandon Wamboldt

Réponses:

19

Jusqu'à présent, votre analyse est généralement correcte. La façon dont un shell peut définir le stdin d'un processus sur un descripteur de canal peut être (pseudocode):

pipe(p) // create a new pipe with two handles p[0] and p[1]
fork() // spawn a child process
    close(p[0]) // close the write end of the pipe in the child
    dup2(p[1], 0) // duplicate the pipe descriptor on top of fd 0 (stdin)
    close(p[1]) // close the other pipe descriptor
    exec() // run a new process with the new descriptors in place
Greg Hewgill
la source
Merci! Juste curieux de savoir pourquoi l' dup2appel est nécessaire, et vous ne pouvez pas simplement assigner directement le descripteur de canal à stdin?
Brandon Wamboldt
3
L'appelant ne peut pas choisir la valeur numérique du descripteur de fichier lors de sa création pipe(). L' dup2()appel permet à l'appelant de copier le descripteur de fichier dans une valeur numérique spécifique (nécessaire car 0, 1, 2 sont stdin, stdout, stderr). C'est l'équivalent du noyau de "assigner directement à stdin". Notez que la variable globale de la bibliothèque d'exécution C stdinest a FILE *, qui n'est pas liée au noyau (bien qu'elle soit initialisée pour être connectée au descripteur 0).
Greg Hewgill
Très bonne réponse! Je suis un peu perdu dans les détails. Vous vous demandez simplement pourquoi vous fermez (p [1]) avant d'exécuter exec ()? Une fois que dup2 revient, p [1] ne pointe-t-il pas vers fd 0? Puis close (p [1]) ferme le descripteur de fichier 0. Alors comment lire à partir du stdin du processus enfant?
user1559897
@ user1559897: L' dup2appel ne change pas p[1]. Au lieu de cela, il crée les deux poignées p[1]et 0pointe vers le même objet noyau (le tuyau). Étant donné que le processus enfant n'a pas besoin de deux poignées stdin (et ne saurait de toute façon quelle est la poignée numérotée p[1]), il p[1]est fermé avant exec.
Greg Hewgill
@GregHewgill Gotchu. THX!
user1559897