Suche…
Einführung
Grundlegende Erstellung und Verwendung
Anonyme Pipes oder einfach Pipes sind vom Kernel verwaltete Objekte, die Prozessen als Dateideskriptorenpaare ausgesetzt werden, einer für den Leseterminus und einer für den Schreibterminus. Sie werden über die Funktion pipe
(2) erstellt:
int pipefds[2];
int result;
result = pipe(pipefds);
Bei Erfolg zeichnet pipe()
den Deskriptor für das Leseende der Pipe am Index 0 des bereitgestellten Arrays und den Deskriptor für das Schreibende am Index 1 auf. Diese Indizes sind analog zu den herkömmlichen Dateibeschreibungsnummern für die Standardströme.
Nachdem Sie eine Pipe erstellt haben, verwenden Sie POSIX-E / A-Funktionen, um in das Schreibende zu schreiben oder vom Leseseite zu lesen:
Prozess 1:
ssize_t bytes_written = write(pipefds[1], "Hello, World!", 14);
Prozess 2:
char buffer[256];
ssize_t bytes_read = read(pipefds[0], buffer, sizeof(buffer));
Alternativ können Sie stattdessen fdopen()
, um eines der beiden Pipe-Ends in eine FILE
Struktur für die Verwendung mit C stdio-Funktionen einzubinden:
FILE *write_end = fdopen(pipefds[1]);
if (write_end) {
fputs("Hello, World!");
}
Pipes haben endliche I / O-Puffer, und normale Schreibvorgänge in Pipes mit vollen Puffern werden blockiert. Pipes sind daher kein sicherer Mechanismus für die Kommunikation eines Threads mit sich selbst, als würde der Thread, der in eine Pipe schreibt, auch der einzige sein, der daraus liest. Sobald ein Thread diesen Thread blockiert, ist er blockiert.
Eine Pipe bleibt bestehen, solange für jeden Prozess eine offene Dateibeschreibung für eines der Pipe-Enden vorhanden ist. Mit der Funktion close
(2) können Sie ein Pipe-Ende schließen, das durch einen Dateideskriptor dargestellt wird. Mit fclose
(3) können Sie ein Pipe-Ende über eine FILE
.
Einrichten einer Pipe zu einem untergeordneten Prozess
Dateideskriptoren und FILE
Objekte sind pro-prozessuale Ressourcen, die nicht selbst über normale E / A zwischen Prozessen ausgetauscht werden können. Damit zwei unterschiedliche Prozesse über eine anonyme Pipe kommunizieren können, müssen ein oder beide beteiligten Prozesse ein offenes Pipe-Ende von dem Prozess erben, der die Pipe erstellt hat. Dies muss der übergeordnete Prozess oder ein weiter entfernter Vorfahr sein. Der einfachste Fall ist der, in dem ein übergeordneter Prozess mit einem untergeordneten Prozess kommunizieren möchte.
Da der untergeordnete Prozess die erforderliche Beschreibung der geöffneten Datei von seinem übergeordneten Element erben muss, muss die Pipe zuerst erstellt werden. Das Elternteil gabelt sich dann. Normalerweise ist jeder Prozess entweder ein strikter Leser oder ein strikter Verfasser. In diesem Fall sollte jeder das Rohrende schließen, das er nicht verwenden möchte.
void demo() {
int pipefds[2];
pid_t pid;
// Create the pipe
if (pipe(pipefds)) {
// error - abort ...
}
switch (pid = fork()) {
case -1:
// error - abort ...
break;
case 0: /* child */
close(pipefds[0]);
write(pipefds[1], "Goodbye, and thanks for all the fish!", 37);
exit(0);
default: /* parent */
close(pipefds[1]);
char buffer[256];
ssize_t nread = read(pipefds[0], sizeof(buffer) - 1);
if (nread >= 0) {
buffer[nread] = '\0';
printf("My child said '%s'\n", buffer);
}
// collect the child
wait(NULL);
break;
}
}
Zwei untergeordnete Prozesse über eine Pipe verbinden
Das Verbinden von zwei untergeordneten Prozessen über eine Pipe wird durchgeführt, indem jedes von zwei Kindern über unterschiedliche Enden derselben Pfeife mit dem übergeordneten Element verbunden wird. Normalerweise nimmt das übergeordnete Element nicht an der Unterhaltung zwischen den untergeordneten Elementen teil, sodass es die Kopien beider Pipe-Ends schließt.
int demo() {
int pipefds[2];
pid_t child1, child2;
if (pipe(pipefds)) {
// error - abort ...
}
switch (child1 = fork()) {
case -1:
// error - abort
break;
case 0: /* child 1 */
close(pipefds[0]);
write(pipefds[1], "Hello, brother!", 15);
exit(0);
default: /* parent */
// nothing
}
switch (child1 = fork()) {
case -1:
// error - abort
break;
case 0: /* child 2 */
char buffer[256];
ssize_t nread;
close(pipefds[1]);
nread = read(pipefds[0], buffer, sizeof(buffer) - 1);
if (nread < 0) {
// handle error
} else {
buffer[nread] = '\0';
printf("My brother told me '%s'\n", buffer);
}
exit(0);
default: /* parent */
// nothing
}
// Only the parent reaches this point
close(pipefds[0]);
close(pipefds[1]);
if (child1 >= 0) {
wait(NULL);
if (child2 >= 0) {
wait(NULL);
}
}
}
Erstellen einer Shell-Pipeline
Eine Shell-artige Pipeline besteht aus zwei oder mehr Prozessen, von denen jeder mit seinem Standardausgang an den Standardeingang des nächsten angeschlossen ist. Die Output-to-Input-Verbindungen sind auf Pipes aufgebaut. Um einen Pipeline-ähnlichen Satz von Prozessen einzurichten, erstellt man Pipe-verbundene dup2
Prozesse, wie in einem anderen Beispiel beschrieben, und verwendet außerdem die Funktion dup2
(2), um jedes Pipe-Ende auf den entsprechenden Standarddateideskriptor seines Prozesses zu kopieren. Im Allgemeinen ist es eine gute Idee, den ursprünglichen Pipe-End-Dateideskriptor zu schließen, insbesondere wenn, wie oftmals, beabsichtigt ist, dass das Kind danach einen anderen Befehl ausführt.
// Most error handling omitted for brevity, including ensuring that pipe ends are
// always closed and child processes are always collected as needed; see other
// examples for more detail.
int demo() {
int pipefds[2];
pid_t child1 = -1, child2 = -1;
pipe(pipefds);
switch (child1 = fork()) {
case -1:
// handle error ...
break;
case 0: /* child 1 */
close(pipefds[0]);
dup2(pipefds[1], STDOUT_FILENO);
close(pipefds[1]);
execl("/bin/cat", "cat", "/etc/motd", NULL);
exit(1); // execl() returns only on error
default: /* parent */
// nothing
}
switch (child2 = fork()) {
case -1:
// handle error ...
break;
case 0: /* child 2 */
close(pipefds[1]);
dup2(pipefds[0], STDIN_FILENO);
close(pipefds[0]);
execl("/bin/grep", "grep", "[Ww]ombat", NULL);
exit(1); // execl() returns only on error
default: /* parent */
// nothing
}
// Only the parent reaches this point
close(pipefds[0]);
close(pipefds[1]);
wait(NULL);
wait(NULL);
}
Einer der häufigsten Fehler beim Einrichten einer Pipeline besteht darin, zu vergessen, dass einer oder mehrere der beteiligten Prozesse die nicht verwendeten Rohrenden schließen. Als Faustregel sollte das Endergebnis sein, dass jedes Rohrende genau einem Prozess gehört und nur in diesem Prozess geöffnet ist.
In einem Fall wie die Demo - Funktion oben, Ausfall das zweite Kind oder des Elternteil schließen pipefds[1]
führt in dem zweiten Kind hängen, für grep
wird auch weiterhin für die Eingabe warten , bis es EOF sieht, und das wird nicht sein Dies wird beobachtet, solange das Schreibende der Pipe in einem Prozess geöffnet bleibt, beispielsweise im übergeordneten Prozess oder im zweiten untergeordneten Prozess.