Named Pipes, Dateideskriptoren und EOF

10

Zwei Fenster, derselbe Benutzer, mit Bash-Eingabeaufforderungen. Geben Sie in Fenster 1 Folgendes ein:

$ mkfifo f; exec <f

Bash versucht nun, aus dem Dateideskriptor 0 zu lesen, der der Named Pipe zugeordnet ist f. Geben Sie in Fenster 2 Folgendes ein:

$ echo ls > f

Jetzt druckt Fenster 1 ein ls und dann stirbt die Shell. Warum?

Nächstes Experiment: Öffnen Sie Fenster-1 erneut mit exec <f. Geben Sie in Fenster 2 Folgendes ein:

$ exec 3>f
$ echo ls >&3

Nach der ersten Zeile oben wird Fenster 1 aktiviert und eine Eingabeaufforderung gedruckt. Warum? Nach der zweiten Zeile oben druckt Fenster-1 die lsAusgabe und die Shell bleibt am Leben. Warum? Tatsächlich schließt jetzt in Fenster-2 echo ls > fdie Fenster-1-Shell nicht.

Die Antwort muss mit der Existenz des Dateideskriptors 3 aus Fenster 2 zu tun haben, der auf die Named Pipe verweist?!

Fixee
quelle
1
Nach exec <f, bashnicht versucht , lesen aus f, wird zunächst versucht , öffnen es. Das open()wird erst zurückkehren, wenn ein Prozess ein weiteres Öffnen im Schreibmodus für die Pipe ausführt (an diesem Punkt wird die Pipe instanziiert und die Shell liest Eingaben von ihr).
Stéphane Chazelas
Hervorragender Punkt, @ StéphaneChazelas. Dies muss der Grund sein, warum exec 3>fdie erste Shell nach dem Ausführen eine Eingabeaufforderung ausgibt. (Kleiner Punkt,
meinten Sie
1
Ja Entschuldigung. Bearbeitet jetzt kurz vor Ablauf der 5 Minuten
Stéphane Chazelas

Antworten:

12

Dies hat mit dem Schließen des Dateideskriptors zu tun .

In Ihrem ersten Beispiel wird echoin den Standardausgabestream geschrieben, mit dem die Shell geöffnet wird, um eine Verbindung herzustellen. fWenn sie beendet wird, wird der Deskriptor (von der Shell) geschlossen. Auf der Empfangsseite wird die Shell, die Eingaben aus ihrem Standardeingabestream (verbunden mit f) liest ls, ausgeführt, ausgeführt lsund dann aufgrund der Bedingung für das Dateiende auf ihrer Standardeingabe beendet.

Die Dateiende-Bedingung tritt auf, weil alle Writer der Named Pipe (in diesem Beispiel nur einer) ihr Pipe-Ende geschlossen haben.

In Ihrem zweiten Beispiel exec 3>föffnet Deskriptordatei 3 zum Schreiben auf f, dann echoschreibt lses. Es ist die Shell, in der jetzt der Dateideskriptor geöffnet ist, nicht der echoBefehl. Der Deskriptor bleibt geöffnet, bis Sie dies tun exec 3>&-. Auf der Empfangsseite wird die Shell, die Eingaben aus ihrem Standardeingabestream (verbunden mit f) liest ls, ausgeführt lsund wartet dann auf weitere Eingaben (da der Stream noch offen ist).

Der Stream bleibt offen, da alle Autoren (die Shell, via exec 3>fund echo) ihr Ende der Pipe nicht geschlossen haben ( exec 3>fist noch in Kraft).


Ich habe echooben darüber geschrieben, als wäre es ein externer Befehl. Es ist höchstwahrscheinlich in die Shell eingebaut. Der Effekt ist jedoch der gleiche.

Kusalananda
quelle
6

Es gibt nicht viel zu tun: Wenn es keine Writer für die Pipe gibt, sieht sie für die Leser geschlossen aus, dh sie gibt beim Lesen EOF zurück und blockiert beim Öffnen.

Von der Linux-Manpage ( pipe(7)siehe aber auch fifo(7)):

Wenn alle Dateideskriptoren, die sich auf das Schreibende einer Pipe beziehen, geschlossen wurden, wird bei einem Versuch, read(2)die Pipe zu verlassen, das Dateiende angezeigt (es read(2)wird 0 zurückgegeben).

Das Schließen des Schreibendes geschieht implizit am Ende des echo ls >f, und wie Sie sagen, wird im anderen Fall der Dateideskriptor offen gehalten.

ilkkachu
quelle
Scheint analog zu Referenzzählungen in Java (und anderen OO-Sprachen) zu sein! Macht aber Sinn.
Fixee
2

Nachdem ich die beiden Antworten von @Kusalananda und @ikkachu gelesen habe, glaube ich zu verstehen. In Fenster 1 wartet die Shell darauf, dass etwas das Schreibende der Pipe öffnet und dann schließt. Sobald das Schreibende geöffnet ist, druckt die Shell in Fenster 1 eine Eingabeaufforderung. Sobald das Schreibende geschlossen ist, erhält die Shell EOF und stirbt.

Auf der Fenster-2-Seite haben wir die beiden in meiner Frage beschriebenen Situationen: In der ersten Situation mit echo ls > fgibt es keinen Dateideskriptor 3, also haben wir echoLaichen, stdinund es stdoutsieht so aus:

0 --> tty
1 --> f

Dann echoendet und die Schale schließt beide Deskriptoren. Da der Dateideskriptor 1 geschlossen ist und referenziert f, ist das Schreibende von fgeschlossen, und dies führt dazu, dass ein EOF zu Fenster 1 wechselt.

In der zweiten Situation laufen wir exec 3>fin unserer Shell, wodurch die Shell diese Umgebung annimmt:

bash:
0 --> tty
1 --> tty
2 --> tty
3 --> f

Jetzt führen wir aus echo ls >& 3und die Shell weist Dateideskriptoren echowie folgt zu:

echo:
0 --> tty
1 --> f     # because 3 points to f
2 --> tty

Dann schließt die Shell die drei obigen Deskriptoren, einschließlich f, hat aber fimmer noch einen Verweis darauf von der Shell selbst. Das ist der wichtige Unterschied. Das Schließen von Deskriptor 3 mit exec 3>&-würde die letzte offene Referenz schließen und einen EOF zu Fenster 1 führen, wie @Kusalananda feststellte.

Fixee
quelle
Dies ist ein gutes Beispiel dafür, warum Sie die ersten drei Dateideskriptoren in Ruhe lassen sollten, es sei denn, es gibt einen guten Entwurfsgrund, sie zu ändern. Wenn Sie den Deskriptor (1) verwendet haben, der letztendlich der Eingabedeskriptor (0) für die andere Shell war, haben Sie nicht nur die Pipe geschlossen (und was Sie mit diesem bestimmten Datenstrom gemacht haben), sondern auch die Eingabe für die zweite Shell, die dazu führte, dass es beendet wurde. Dies ist in Ordnung, aber nur, wenn Sie es absichtlich tun. Durch die Verwendung von Dateideskriptoren mit höheren Nummern werden solche Nebenwirkungen vermieden, da nichts erwartet, dass sie sich in einem bestimmten Zustand befinden oder sogar definiert sind.
Joe
Um ehrlich zu sein, ich bin mir nicht sicher, was ich in diesem Kommentar sagen wollte, ich werde es einfach löschen.
Stéphane Chazelas