Ich habe die andere Frage recherchiert , als mir klar wurde, dass ich nicht verstehe, was unter der Haube passiert, was diese /dev/fd/*
Dateien sind und warum untergeordnete Prozesse sie öffnen können.
Ich habe die andere Frage recherchiert , als mir klar wurde, dass ich nicht verstehe, was unter der Haube passiert, was diese /dev/fd/*
Dateien sind und warum untergeordnete Prozesse sie öffnen können.
Antworten:
Nun, es gibt viele Aspekte.
Dateideskriptoren
Für jeden Prozess verwaltet der Kernel eine Tabelle mit geöffneten Dateien (möglicherweise ist diese Tabelle anders implementiert, aber da Sie sie sowieso nicht sehen können, können Sie davon ausgehen, dass es sich um eine einfache Tabelle handelt). Diese Tabelle enthält Informationen darüber, um welche Datei es sich handelt / wo sie sich befindet, in welchem Modus Sie sie geöffnet haben, an welcher Position Sie gerade lesen / schreiben und was sonst noch benötigt wird, um tatsächlich E / A-Operationen an dieser Datei durchzuführen. Jetzt kann der Prozess diese Tabelle nie mehr lesen (oder sogar schreiben). Wenn der Prozess eine Datei öffnet, erhält er einen sogenannten Dateideskriptor zurück. Welches ist einfach ein Index in die Tabelle.
Das Verzeichnis /dev/fd
und sein Inhalt
Unter Linux dev/fd
ist das eigentlich eine symbolische Verknüpfung zu /proc/self/fd
. /proc
ist ein Pseudodateisystem, in dem der Kernel mehrere interne Datenstrukturen abbildet, auf die mit der Datei-API zugegriffen werden kann (sie sehen also wie normale Dateien / Verzeichnisse / Symlinks zu den Programmen aus). Insbesondere gibt es Informationen zu allen Prozessen (was ihm den Namen gab). Die symbolische Verknüpfung /proc/self
bezieht sich immer auf das Verzeichnis, das dem aktuell ausgeführten Prozess zugeordnet ist (dh auf den Prozess, der ihn anfordert; verschiedene Prozesse sehen daher unterschiedliche Werte). Im Verzeichnis des Prozesses befindet sich ein Unterverzeichnisfd
Die Datei enthält für jede geöffnete Datei eine symbolische Verknüpfung, deren Name nur die dezimale Darstellung des Dateideskriptors ist (der Index in der Dateitabelle des Prozesses, siehe vorherigen Abschnitt) und deren Ziel die Datei ist, der sie entspricht.
Dateideskriptoren beim Erstellen von untergeordneten Prozessen
Ein untergeordneter Prozess wird von a erstellt fork
. A erstellt fork
eine Kopie der Dateideskriptoren. Dies bedeutet, dass der erstellte untergeordnete Prozess dieselbe Liste offener Dateien enthält wie der übergeordnete Prozess. Wenn eine der geöffneten Dateien nicht vom untergeordneten Element geschlossen wird, greift der Zugriff auf einen geerbten Dateideskriptor im untergeordneten Element auf dieselbe Datei zu wie der Zugriff auf den ursprünglichen Dateideskriptor im übergeordneten Prozess.
Beachten Sie, dass Sie nach einem Fork zunächst zwei Kopien desselben Prozesses haben, die sich nur im Rückgabewert vom Fork-Aufruf unterscheiden (das Elternteil erhält die PID des Kindes, das Kind erhält 0). Normalerweise folgt einem Fork ein exec
, um eine der Kopien durch eine andere ausführbare Datei zu ersetzen. Die offenen Dateideskriptoren überleben diesen exec. Beachten Sie auch, dass der Prozess vor der Ausführung andere Manipulationen ausführen kann (z. B. das Schließen von Dateien, die der neue Prozess nicht erhalten sollte, oder das Öffnen anderer Dateien).
Unbenannte Rohre
Eine unbenannte Pipe ist nur ein Paar von Dateideskriptoren, die auf Anforderung des Kernels erstellt wurden, sodass alles, was in den ersten Dateideskriptor geschrieben wurde, an den zweiten übergeben wird. Am häufigsten wird das Piping-Konstrukt foo | bar
von verwendet bash
, bei dem die Standardausgabe von foo
durch den Schreibteil der Pipe und die Standardeingabe durch den Leseteil ersetzt wird. Standardeingabe und Standardausgabe sind nur die ersten beiden Einträge in der Dateitabelle (Eintrag 0 und 1; 2 ist Standardfehler), und daher bedeutet das Ersetzen dieses Eintrags nur das Umschreiben dieses Tabelleneintrags mit den Daten, die dem anderen Dateideskriptor entsprechen (wieder der Die tatsächliche Implementierung kann davon abweichen. Da der Prozess nicht direkt auf die Tabelle zugreifen kann, gibt es dafür eine Kernelfunktion.
Prozessersetzung
Jetzt haben wir alles zusammen, um zu verstehen, wie die Prozessersetzung funktioniert:
echo
Prozess. Der untergeordnete Prozess (der eine exakte Kopie des ursprünglichen bash
Prozesses ist) schließt das Leseende der Pipe und ersetzt die eigene Standardausgabe durch das Schreibende der Pipe. echo
Vorausgesetzt, dass es sich um eine eingebaute Shell handelt, erspart sich bash
möglicherweise den exec
Aufruf, spielt jedoch keine Rolle (die eingebaute Shell ist möglicherweise ebenfalls deaktiviert und wird in diesem Fall ausgeführt /bin/echo
).<(echo 1)
durch die Pseudodateiverknüpfung in /dev/fd
Bezug auf das Leseende der unbenannten Pipe./dev/fd/
. Da der entsprechende Dateideskriptor noch offen ist, entspricht er immer noch dem Leseende der Pipe. Wenn das PHP-Programm die angegebene Datei zum Lesen öffnet, erstellt es tatsächlich einen second
Dateideskriptor für das Leseende der unbenannten Pipe. Aber das ist kein Problem, das könnte man auch lesen.echo
Befehls, der an das Schreibende derselben Pipe geht.php
Szenario, aber es php
geht nicht gut mit Rohren um . In Anbetracht des Kommandos cat <(echo test)
ist das Seltsame hier, dass sich die bash
Gabeln einmal cat
, aber zweimal teilen echo test
.
Das Ausleihen aus celtschk
der Antwort /dev/fd
ist eine symbolische Verknüpfung zu /proc/self/fd
. Und /proc
ist ein Pseudo-Dateisystem, das Informationen über Prozesse und andere Systeminformationen in einer hierarchischen dateiähnlichen Struktur darstellt. Dateien in /dev/fd
entsprechen Dateien, die von einem Prozess geöffnet wurden, und haben einen Dateideskriptor als Namen und Dateien selbst als Ziele. Das Öffnen der Datei /dev/fd/N
entspricht dem Duplizieren des Deskriptors N
(vorausgesetzt, der Deskriptor N
ist geöffnet).
Und hier sind die Ergebnisse meiner Untersuchung, wie es funktioniert (die strace
Ausgabe ist frei von unnötigen Details und wurde modifiziert, um besser auszudrücken, was passiert):
$ cat 1.c
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
char buf[100];
int fd;
fd = open(argv[1], O_RDONLY);
read(fd, buf, 100);
write(STDOUT_FILENO, buf, n_read);
return 0;
}
$ gcc 1.c -o 1.out
$ cat 2.c
#include <unistd.h>
#include <string.h>
int main(void)
{
char *p = "hello, world\n";
write(STDOUT_FILENO, p, strlen(p));
return 0;
}
$ gcc 2.c -o 2.out
$ strace -f -e pipe,fcntl,dup2,close,clone,close,execve,wait4,read,open,write bash -c './1.out <(./2.out)'
[bash] pipe([3, 4]) = 0
[bash] dup2(3, 63) = 63
[bash] close(3) = 0
[bash] clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7c211fb9d0) = p2
Process p2 attached
[bash] close(4) = 0
[bash] clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7c211fb9d0) = p1
Process p1 attached
[bash] close(63) = 0
[p2] dup2(4, 1) = 1
[p2] close(4) = 0
[p2] close(63) = 0
[bash] wait4(-1, <unfinished ...>
Process bash suspended
[p1] execve("/home/yuri/_/1.out", ["/home/yuri/_/1.out", "/dev/fd/63"], [/* 31 vars */]) = 0
[p2] clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7c211fb9d0) = p22
Process p22 attached
[p22] execve("/home/yuri/_/2.out", ["/home/yuri/_/2.out"], [/* 31 vars */]) = 0
[p2] wait4(-1, <unfinished ...>
Process p2 suspended
[p1] open("/dev/fd/63", O_RDONLY) = 3
[p1] read(3, <unfinished ...>
[p22] write(1, "hello, world\n", 13) = 13
[p1] <... read resumed> "hello, world\n", 100) = 13
Process p2 resumed
Process p22 detached
[p1] write(1, "hello, world\n", 13) = 13
hello, world
[p2] <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = p22
[p2] --- SIGCHLD (Child exited) @ 0 (0) ---
[p2] wait4(-1, 0x7fff190f289c, WNOHANG, NULL) = -1 ECHILD (No child processes)
Process bash resumed
Process p1 detached
[bash] <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = p1
[bash] --- SIGCHLD (Child exited) @ 0 (0) ---
Process p2 detached
[bash] wait4(-1, 0x7fff190f2bdc, WNOHANG, NULL) = 0
--- SIGCHLD (Child exited) @ 0 (0) ---
[bash] wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG, NULL) = p2
[bash] wait4(-1, 0x7fff190f299c, WNOHANG, NULL) = -1 ECHILD (No child processes)
Erstellt im bash
Allgemeinen eine Pipe und übergibt ihre Enden als Dateideskriptoren an ihre untergeordneten Elemente (Leseende an 1.out
und Schreibende an 2.out
). Und übergibt read end als Befehlszeilenparameter an 1.out
( /dev/fd/63
). Dieser Weg 1.out
kann sich öffnen /dev/fd/63
.