Wie Pipes unter Linux funktionieren

25

Ich habe nachgelesen, wie Pipes im Linux-Kernel implementiert sind, und wollte mein Verständnis überprüfen. Wenn ich falsch liege, wird die Antwort mit der richtigen Erklärung ausgewählt.

  • Linux hat ein VFS namens pipefs, das im Kernel eingehängt ist (nicht im User Space)
  • pipefs hat einen einzelnen Superblock und wird daneben an seiner eigenen root ( pipe:) gemountet/
  • Pipefs können im Gegensatz zu den meisten Dateisystemen nicht direkt angezeigt werden
  • Der Zugang zu pipefs erfolgt über den pipe(2)Syscall
  • Der pipe(2)Systemaufruf, der von Shells für die Weiterleitung an den |Operator (oder manuell von einem anderen Prozess) verwendet wird, erstellt eine neue Datei in Pipefs, die sich so ziemlich wie eine normale Datei verhält
  • Die Datei auf der linken Seite des Pipe-Operators wird stdoutin die temporäre Datei umgeleitet, die in pipefs erstellt wurde
  • Die Datei auf der rechten Seite des Pipe-Operators ist stdinauf die Datei auf Pipefs eingestellt
  • pipefs ist im Speicher gespeichert und sollte durch Kernelmagie nicht ausgelagert werden

Ist diese Erklärung, wie Pipes (z. B. ls -la | less) funktionieren, ziemlich korrekt?

Eine Sache, die ich nicht verstehe, ist, wie so etwas wie Bash einen Prozess stdinoder stdoutden von zurückgegebenen Dateideskriptor setzen würde pipe(2). Darüber habe ich noch nichts gefunden.

Brandon Wamboldt
quelle
Beachten Sie, dass es sich um zwei erheblich unterschiedliche Ebenen mit demselben Namen handelt. Der pipe()Kernel-Aufruf ist zusammen mit der Maschinerie, die ihn unterstützt ( pipefsusw.), viel niedriger als der |Operator, der in Ihrer Shell angeboten wird. Letzteres wird normalerweise mit dem ersteren implementiert, muss es aber nicht sein.
Greg Hewgill
Ja, ich beziehe mich speziell auf die Operationen auf niedrigerer Ebene, mit der Annahme, dass der |Operator nur pipe(2)als ein Prozess aufruft , wie es Bash tut.
Brandon Wamboldt

Antworten:

19

Ihre bisherige Analyse ist im Allgemeinen richtig. Die Art und Weise, wie eine Shell den Standardwert eines Prozesses auf einen Pipe-Deskriptor setzt, könnte sein (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
quelle
Vielen Dank! Nur neugierig, warum der dup2Aufruf benötigt wird, und Sie können den Pipe-Deskriptor nicht einfach direkt stdin zuweisen?
Brandon Wamboldt
3
Der Aufrufer kann nicht auswählen, wie der numerische Wert des Dateideskriptors lautet, wenn er erstellt wird pipe(). Der dup2()Aufruf ermöglicht dem Aufrufer, den Dateideskriptor auf einen bestimmten numerischen Wert zu kopieren (erforderlich, da 0, 1, 2 stdin, stdout, stderr sind). Das ist das Kernel-Äquivalent von "stdin direkt zuweisen". Beachten Sie, dass die globale Variable der C-Laufzeitbibliothek eine Variable stdinist FILE *, die nicht kernelbezogen ist (obwohl sie für die Verbindung mit Deskriptor 0 initialisiert wurde).
Greg Hewgill
Gute Antwort! Ich bin ein bisschen im Detail verloren. Fragen Sie sich nur, warum Sie (p [1]) schließen, bevor Sie exec () ausführen? Wenn dup2 zurückkommt, würde p [1] nicht auf fd 0 zeigen? Dann schließt close (p [1]) den Dateideskriptor 0. Wie können wir dann aus dem Standard des untergeordneten Prozesses lesen?
user1559897
@ user1559897: Der dup2Aufruf wird nicht geändert p[1]. Stattdessen werden die beiden Ziehpunkte erstellt p[1]und 0auf dasselbe Kernelobjekt (die Pipe) verwiesen. Da der untergeordnete Prozess keine zwei Standard-Handles benötigt (und ohnehin nicht wissen würde, was das nummerierte Handle p[1]ist), p[1]wird er zuvor geschlossen exec.
Greg Hewgill
@ GregHewgill Gotchu. Danke!
user1559897