Ist dies mehrere Coderohre in C ist sinnvoll?
Frage
Ich habe eine Frage dazu erstellt ein paar Tage . Meine Lösung ist etwas in den Linien von dem, was in der akzeptierten Antwort vorgeschlagen wurde. Allerdings kam ein Freund von dir mit folgenden Lösung:
Bitte beachten Sie, dass der Code ein paar Mal (überprüfen Sie die bearbeiten Versionen) wurde aktualisiert, unter die Vorschläge in den Antworten zu reflektieren. Wenn Sie beabsichtigen, eine neue Antwort zu geben, tun Sie dies bitte mit diesem neuen Code im Auge und nicht die alten, die viele Probleme hatten.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[]){
int fd[2], i, aux, std0, std1;
do {
std0 = dup(0); // backup stdin
std1 = dup(1); // backup stdout
// let's pretend I'm reading commands here in a shell prompt
READ_COMMAND_FROM_PROMPT();
for(i=1; i<argc; i++) {
// do we have a previous command?
if(i > 1) {
dup2(aux, 0);
close(aux);
}
// do we have a next command?
if(i < argc-1) {
pipe(fd);
aux = fd[0];
dup2(fd[1], 1);
close(fd[1]);
}
// last command? restore stdout...
if(i == argc-1) {
dup2(std1, 1);
close(std1);
}
if(!fork()) {
// if not last command, close all pipe ends
// (the child doesn't use them)
if(i < argc-1) {
close(std0);
close(std1);
close(fd[0]);
}
execlp(argv[i], argv[i], NULL);
exit(0);
}
}
// restore stdin to be able to keep using the shell
dup2(std0, 0);
close(std0);
}
return 0;
}
Dies simuliert eine Reihe von Befehlen durch die Leitungen wie in der Bash, zum Beispiel: cmd1 | cmd2 | ... | cmd_n. Ich sage „simulieren“, weil, wie Sie sehen können, werden die Befehle tatsächlich von den Argumenten lesen. Nur zu ersparen Zeit eine einfache Shell-Prompt Kodierung ...
Natürlich gibt es einige Probleme zu beheben und wie Fehlerbehandlung hinzuzufügen, aber das ist nicht der Punkt hier. Ich denke, dass ich irgendwie den Code bekommen, aber es macht mir immer noch viel verwirrend, wie das Ganze funktioniert.
Bin ich etwas fehlt oder das wirklich funktioniert und es ist eine schöne und saubere Lösung, das Problem zu lösen? Wenn nicht, kann mir jemand zeigen die entscheidenden Probleme dieser Code hat?
Lösung
Sieht vernünftig, wenn es wirklich beheben muss undichten std
und aux
an die Kinder und nach der Schleife und die Eltern Original stdin
ist für immer verloren.
Dies wäre wahrscheinlich besser mit Farbe ...
./a.out foo bar baz <stdin >stdout std = dup(stdout) || |+==========================std || || || pipe(fd) || || pipe1[0] -- pipe0[1] || || || || || || aux = fd[0] || || aux || || || XX || || || || /-------++----------+| || dup2(fd[1], 1) || // || || || || || || || || close(fd[1]) || || || XX || || || || || fork+exec(foo) || || || || XX || || || /-----++-------+| || dup2(aux, 0) // || || || || || || || close(aux) || || XX || || || || pipe(fd) || || pipe2[0] -- pipe2[1] || || || || || || aux = fd[0] || || aux || || || XX || || || || /-------++----------+| || dup2(fd[1], 1) || // || || || || || || || || close(fd[1]) || || || XX || || || || || fork+exec(bar) || || || || XX || || || /-----++-------+| || dup2(aux, 0) // || || || || || || || close(aux) || || XX || || || || pipe(fd) || || pipe3[0] -- pipe3[1] || || || || || || aux = fd[0] || || aux || || || XX || || || || /-------++----------+| || dup2(fd[1], 1) || // || || || || || || || || close(fd[1]) || || || XX || || XX || || || /-------++-----------------+| dup2(std, 1) || // || || || || || || fork+exec(baz) || || || ||
-
foo
bekommtstdin=stdin
,stdout=pipe1[1]
-
bar
bekommtstdin=pipe1[0]
,stdout=pipe2[1]
-
baz
bekommtstdin=pipe2[0]
,stdout=stdout
Mein Vorschlag unterscheidet, dass es vermeidet die Eltern stdin
und stdout
Mangeln, nur um sie im Kind zu manipulieren, und leckt nie FDs. Es ist ein bisschen schwieriger zu Diagramm, though.
for cmd in cmds
if there is a next cmd
pipe(new_fds)
fork
if child
if there is a previous cmd
dup2(old_fds[0], 0)
close(old_fds[0])
close(old_fds[1])
if there is a next cmd
close(new_fds[0])
dup2(new_fds[1], 1)
close(new_fds[1])
exec cmd || die
else
if there is a previous cmd
close(old_fds[0])
close(old_fds[1])
if there is a next cmd
old_fds = new_fds
parent cmds = [foo, bar, baz] fds = {0: stdin, 1: stdout} cmd = cmds[0] { there is a next cmd { pipe(new_fds) new_fds = {3, 4} fds = {0: stdin, 1: stdout, 3: pipe1[0], 4: pipe1[1]} } fork => child there is a next cmd { close(new_fds[0]) fds = {0: stdin, 1: stdout, 4: pipe1[1]} dup2(new_fds[1], 1) fds = {0: stdin, 1: pipe1[1], 4: pipe1[1]} close(new_fds[1]) fds = {0: stdin, 1: pipe1[1]} } exec(cmd) there is a next cmd { old_fds = new_fds old_fds = {3, 4} } } cmd = cmds[1] { there is a next cmd { pipe(new_fds) new_fds = {5, 6} fds = {0: stdin, 1: stdout, 3: pipe1[0], 4: pipe1[1], 5: pipe2[0], 6: pipe2[1]} } fork => child there is a previous cmd { dup2(old_fds[0], 0) fds = {0: pipe1[0], 1: stdout, 3: pipe1[0], 4: pipe1[1], 5: pipe2[0], 6: pipe2[1]} close(old_fds[0]) fds = {0: pipe1[0], 1: stdout, 4: pipe1[1], 5: pipe2[0] 6: pipe2[1]} close(old_fds[1]) fds = {0: pipe1[0], 1: stdout, 5: pipe2[0], 6: pipe2[1]} } there is a next cmd { close(new_fds[0]) fds = {0: pipe1[0], 1: stdout, 6: pipe2[1]} dup2(new_fds[1], 1) fds = {0: pipe1[0], 1: pipe2[1], 6: pipe2[1]} close(new_fds[1]) fds = {0: pipe1[0], 1: pipe1[1]} } exec(cmd) there is a previous cmd { close(old_fds[0]) fds = {0: stdin, 1: stdout, 4: pipe1[1], 5: pipe2[0], 6: pipe2[1]} close(old_fds[1]) fds = {0: stdin, 1: stdout, 5: pipe2[0], 6: pipe2[1]} } there is a next cmd { old_fds = new_fds old_fds = {3, 4} } } cmd = cmds[2] { fork => child there is a previous cmd { dup2(old_fds[0], 0) fds = {0: pipe2[0], 1: stdout, 5: pipe2[0], 6: pipe2[1]} close(old_fds[0]) fds = {0: pipe2[0], 1: stdout, 6: pipe2[1]} close(old_fds[1]) fds = {0: pipe2[0], 1: stdout} } exec(cmd) there is a previous cmd { close(old_fds[0]) fds = {0: stdin, 1: stdout, 6: pipe2[1]} close(old_fds[1]) fds = {0: stdin, 1: stdout} } }
Bearbeiten
Ihr aktualisierte Code führt die bisherigen FD Lecks beheben ... aber fügt ein: Sie jetzt std0
die Kinder undicht. Als Jon sagt, ist dies wahrscheinlich die meisten Programme nicht gefährlich ... aber man sollte immer noch eine bessere behaved Schale als diese schreiben.
Auch wenn es vorübergehend ist, würde ich Ihre eigene Shell-Standard in / out / err (0/1/2) wird dringend empfohlen gegen Mangeln, nur so in das Kind richtig machen, bevor exec. Warum? Angenommen, Sie einige printf
Debuggen in der Mitte hinzufügen, oder Sie müssen aufgrund einer Fehlerbedingung retten. Sie werden in Schwierigkeiten, wenn Sie Ihren verkorksten Standard Filedeskriptoren zuerst nicht bereinigen. Bitte, im Interesse der mit Dingen arbeiten, wie in unerwarteten Szenarien sogar erwartet , nicht mit ihnen Dreck, bis Sie müssen.
Bearbeiten
Wie ich in anderen Kommentaren erwähnt, Aufspaltung in kleinere Teile macht es viel einfacher zu verstehen. Dieser kleine Helfer sollte leicht verständlich und fehlerfrei sein:
/* cmd, argv: passed to exec
* fd_in, fd_out: when not -1, replaces stdin and stdout
* return: pid of fork+exec child
*/
int fork_and_exec_with_fds(char *cmd, char **argv, int fd_in, int fd_out) {
pid_t child = fork();
if (fork)
return child;
if (fd_in != -1 && fd_in != 0) {
dup2(fd_in, 0);
close(fd_in);
}
if (fd_out != -1 && fd_in != 1) {
dup2(fd_out, 1);
close(fd_out);
}
execvp(cmd, argv);
exit(-1);
}
Wie soll dies:
void run_pipeline(int num, char *cmds[], char **argvs[], int pids[]) {
/* initially, don't change stdin */
int fd_in = -1, fd_out;
int i;
for (i = 0; i < num; i++) {
int fd_pipe[2];
/* if there is a next command, set up a pipe for stdout */
if (i + 1 < num) {
pipe(fd_pipe);
fd_out = fd_pipe[1];
}
/* otherwise, don't change stdout */
else
fd_out = -1;
/* run child with given stdin/stdout */
pids[i] = fork_and_exec_with_fds(cmds[i], argvs[i], fd_in, fd_out);
/* nobody else needs to use these fds anymore
* safe because close(-1) does nothing */
close(fd_in);
close(fd_out);
/* set up stdin for next command */
fd_in = fd_pipe[0];
}
}
Sie können sehen, Bash 's execute_cmd.c#execute_disk_command
von execute_cmd.c#execute_pipeline
genannt wird, xsh 's process.c#process_run
von jobs.c#job_run
genannt zu werden, und sogar jeder einzelne von BusyBox 's verschiedene kleine und minimal shells spaltet sie.
Andere Tipps
Das Hauptproblem ist, dass Sie ein Bündel von Rohren erstellen und machen nicht sicher, dass alle Enden geschlossen sind. Wenn Sie ein Rohr erstellen, erhalten Sie zwei Dateideskriptoren; wenn Sie die Gabel, dann haben Sie vier Dateideskriptoren. Wenn man ein Ende des Rohres mit einem Standard-Deskriptor dup()
oder dup2()
, müssen Sie die beiden Enden des Rohrs schließen, -. Mindestens eines der geschlossen nach der DUP sein muss () oder dup2 () Befehl
, um die Datei-Deskriptoren verfügbar auf den ersten Befehl Betrachten (vorausgesetzt, es gibt mindestens zwei - etwas, das im Allgemeinen (keine pipe()
oder I / O-Umleitung benötigte mit nur einem Befehl) behandelt werden soll, aber ich erkennen, dass die Fehlerbehandlung eliminiert den Code für SO zu halten):
std=dup(1); // Likely: std = 3
pipe(fd); // Likely: fd[0] = 4, fd[1] = 5
aux = fd[0];
dup2(fd[1], 1);
close(fd[1]); // Closes 5
if (fork() == 0) {
// Need to close: fd[0] aka aux = 4
// Need to close: std = 3
close(fd[0]);
close(std);
execlp(argv[i], argv[i], NULL);
exit(1);
}
Beachten Sie, dass, weil fd[0]
in dem Kind nicht geschlossen ist, wird das Kind EOF nie auf seiner Standardeingabe erhalten; dies ist in der Regel problematisch. Die Nicht-Schließung von std
weniger kritisch ist.
geänderten Code Neubetrachtung (Stand: 2009-06-03T20: 52-07: 00) ...
Angenommen, Prozeß beginnt mit Datei-Deskriptoren, 0, 1, 2 (Standard-Eingabe, Ausgabe, Fehler) nur offen. Auch gehen wir davon aus genau drei Befehle zu verarbeiten. Nach wie vor schreibt dieser Code aus der Schleife mit Anmerkungen.
std0 = dup(0); // backup stdin - 3
std1 = dup(1); // backup stdout - 4
// Iteration 1 (i == 1)
// We have another command
pipe(fd); // fd[0] = 5; fd[1] = 6
aux = fd[0]; // aux = 5
dup2(fd[1], 1);
close(fd[1]); // 6 closed
// Not last command
if (fork() == 0) {
// Not last command
close(std1); // 4 closed
close(fd[0]); // 5 closed
// Minor problemette: 3 still open
execlp(argv[i], argv[i], NULL);
}
// Parent has open 3, 4, 5 - no problem
// Iteration 2 (i == 2)
// There was a previous command
dup2(aux, 0); // stdin now on read end of pipe
close(aux); // 5 closed
// We have another command
pipe(fd); // fd[0] = 5; fd[1] = 6
aux = fd[0];
dup2(fd[1], 1);
close(fd[1]); // 6 closed
// Not last command
if (fork() == 0) {
// Not last command
close(std1); // 4 closed
close(fd[0]); // 5 closed
// As before, 3 is still open - not a major problem
execlp(argv[i], argv[i], NULL);
}
// Parent has open 3, 4, 5 - no problem
// Iteration 3 (i == 3)
// We have a previous command
dup2(aux, 0); // stdin is now read end of pipe
close(aux); // 5 closed
// No more commands
// Last command - restore stdout...
dup2(std1, 1); // stdin is back where it started
close(std1); // 4 closed
if (fork() == 0) {
// Last command
// 3 still open
execlp(argv[i], argv[i], NULL);
}
// Parent has closed 4 when it should not have done so!!!
// End of loop
// restore stdin to be able to keep using the shell
dup2(std0, 0);
// 3 still open - as desired
Also, alle Kinder die ursprüngliche Standardeingabe angeschlossen haben als Dateideskriptor 3. Das ist nicht ideal, aber es ist nicht furchtbar traumatischer ist; Ich bin ein Umstand schwer zu finden, wo dies wäre egal.
Schluss Dateideskriptors 4 in der Mutter ist ein Fehler -. Die nächste Iteration der ‚einen Befehl und Prozess gelesen wird es nicht funktionieren, weil std1
nicht innerhalb der Schleife initialisiert
Im Allgemeinen ist dies nahe zu korrigieren -. Aber nicht ganz richtig
Es werden die Ergebnisse geben, einige, die nicht erwartet werden. Es ist weit von einer schönen Lösung. Es verwirrt mit dem Standard-Deskriptoren Eltern-Prozesses, erholt nicht die Standardeingabe, Deskriptoren für Kinder auslaufen, etc
Wenn Sie rekursiv denken, kann es leichter zu verstehen sein. Im Folgenden finden Sie eine korrekte Lösung, ohne Fehlerprüfung. Betrachten Sie eine verknüpfte Liste Typ command
, mit seinen next
Zeiger und ein argv
Array.
void run_pipeline(command *cmd, int input) {
int pfds[2] = { -1, -1 };
if (cmd->next != NULL) {
pipe(pfds);
}
if (fork() == 0) { /* child */
if (input != -1) {
dup2(input, STDIN_FILENO);
close(input);
}
if (pfds[1] != -1) {
dup2(pfds[1], STDOUT_FILENO);
close(pfds[1]);
}
if (pfds[0] != -1) {
close(pfds[0]);
}
execvp(cmd->argv[0], cmd->argv);
exit(1);
}
else { /* parent */
if (input != -1) {
close(input);
}
if (pfds[1] != -1) {
close(pfds[1]);
}
if (cmd->next != NULL) {
run_pipeline(cmd->next, pfds[0]);
}
}
}
Nennen Sie es mit dem ersten Befehl in der verketteten Liste und input
= -1. Es tut ihr Übriges.
Sowohl in dieser Frage und in andere (wie im ersten Post verbunden ist), ephemient schlug mir eine Lösung für das Problem, ohne mit dem Eltern Filedeskriptoren durcheinander, wie durch eine mögliche Lösung in dieser Frage gezeigt, .
Ich habe nicht seine Lösung, habe ich versucht und versucht zu verstehen, aber ich kann es nicht zu bekommen scheinen. Ich habe auch versucht es ohne Verständnis zu codieren, aber es hat nicht funktioniert. Wahrscheinlich, weil ich versagt habe es richtig zu verstehen und war nicht in der Lage, es codieren die es codiert werden soll.
Wie auch immer, habe ich versucht, mit meiner eigenen Lösung mit einigen der Dinge, die ich aus dem Pseudo-Code verstanden zu kommen und kam mit dieser:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
#include <string.h>
#include <readline/readline.h>
#include <readline/history.h>
#define NUMPIPES 5
#define NUMARGS 10
int main(int argc, char *argv[]) {
char *bBuffer, *sPtr, *aPtr = NULL, *pipeComms[NUMPIPES], *cmdArgs[NUMARGS];
int aPipe[2], bPipe[2], pCount, aCount, i, status;
pid_t pid;
using_history();
while(1) {
bBuffer = readline("\e[1;31mShell \e[1;32m# \e[0m");
if(!strcasecmp(bBuffer, "exit")) {
return 0;
}
if(strlen(bBuffer) > 0) {
add_history(bBuffer);
}
sPtr = bBuffer;
pCount =0;
do {
aPtr = strsep(&sPtr, "|");
if(aPtr != NULL) {
if(strlen(aPtr) > 0) {
pipeComms[pCount++] = aPtr;
}
}
} while(aPtr);
cmdArgs[pCount] = NULL;
for(i = 0; i < pCount; i++) {
aCount = 0;
do {
aPtr = strsep(&pipeComms[i], " ");
if(aPtr != NULL) {
if(strlen(aPtr) > 0) {
cmdArgs[aCount++] = aPtr;
}
}
} while(aPtr);
cmdArgs[aCount] = NULL;
// Do we have a next command?
if(i < pCount-1) {
// Is this the first, third, fifth, etc... command?
if(i%2 == 0) {
pipe(aPipe);
}
// Is this the second, fourth, sixth, etc... command?
if(i%2 == 1) {
pipe(bPipe);
}
}
pid = fork();
if(pid == 0) {
// Is this the first, third, fifth, etc... command?
if(i%2 == 0) {
// Do we have a previous command?
if(i > 0) {
close(bPipe[1]);
dup2(bPipe[0], STDIN_FILENO);
close(bPipe[0]);
}
// Do we have a next command?
if(i < pCount-1) {
close(aPipe[0]);
dup2(aPipe[1], STDOUT_FILENO);
close(aPipe[1]);
}
}
// Is this the second, fourth, sixth, etc... command?
if(i%2 == 1) {
// Do we have a previous command?
if(i > 0) {
close(aPipe[1]);
dup2(aPipe[0], STDIN_FILENO);
close(aPipe[0]);
}
// Do we have a next command?
if(i < pCount-1) {
close(bPipe[0]);
dup2(bPipe[1], STDOUT_FILENO);
close(bPipe[1]);
}
}
execvp(cmdArgs[0], cmdArgs);
exit(1);
} else {
// Do we have a previous command?
if(i > 0) {
// Is this the first, third, fifth, etc... command?
if(i%2 == 0) {
close(bPipe[0]);
close(bPipe[1]);
}
// Is this the second, fourth, sixth, etc... command?
if(i%2 == 1) {
close(aPipe[0]);
close(aPipe[1]);
}
}
// wait for the last command? all others will run in the background
if(i == pCount-1) {
waitpid(pid, &status, 0);
}
// I know they will be left as zombies in the table
// Not relevant for this...
}
}
}
return 0;
}
Das ist vielleicht nicht die beste und sauberste Lösung, aber es ist etwas, was ich tun kann, und vor allem, was ich verstehen kann. Was gut ist, etwas zum Laufen zu haben, die ich nicht verstehe, und dann bin ich von meinem Lehrer ausgewertet, und ich kann ihn nicht erklären, was der Code macht?
Wie auch immer, was denken Sie über diese ein?
Das ist mein "final" Code mit ephemient Vorschläge:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
#include <string.h>
#include <readline/readline.h>
#include <readline/history.h>
#define NUMPIPES 5
#define NUMARGS 10
int main(int argc, char *argv[]) {
char *bBuffer, *sPtr, *aPtr = NULL, *pipeComms[NUMPIPES], *cmdArgs[NUMARGS];
int newPipe[2], oldPipe[2], pCount, aCount, i, status;
pid_t pid;
using_history();
while(1) {
bBuffer = readline("\e[1;31mShell \e[1;32m# \e[0m");
if(!strcasecmp(bBuffer, "exit")) {
return 0;
}
if(strlen(bBuffer) > 0) {
add_history(bBuffer);
}
sPtr = bBuffer;
pCount = -1;
do {
aPtr = strsep(&sPtr, "|");
if(aPtr != NULL) {
if(strlen(aPtr) > 0) {
pipeComms[++pCount] = aPtr;
}
}
} while(aPtr);
cmdArgs[++pCount] = NULL;
for(i = 0; i < pCount; i++) {
aCount = -1;
do {
aPtr = strsep(&pipeComms[i], " ");
if(aPtr != NULL) {
if(strlen(aPtr) > 0) {
cmdArgs[++aCount] = aPtr;
}
}
} while(aPtr);
cmdArgs[++aCount] = NULL;
// do we have a next command?
if(i < pCount-1) {
pipe(newPipe);
}
pid = fork();
if(pid == 0) {
// do we have a previous command?
if(i > 0) {
close(oldPipe[1]);
dup2(oldPipe[0], 0);
close(oldPipe[0]);
}
// do we have a next command?
if(i < pCount-1) {
close(newPipe[0]);
dup2(newPipe[1], 1);
close(newPipe[1]);
}
// execute command...
execvp(cmdArgs[0], cmdArgs);
exit(1);
} else {
// do we have a previous command?
if(i > 0) {
close(oldPipe[0]);
close(oldPipe[1]);
}
// do we have a next command?
if(i < pCount-1) {
oldPipe[0] = newPipe[0];
oldPipe[1] = newPipe[1];
}
// wait for last command process?
if(i == pCount-1) {
waitpid(pid, &status, 0);
}
}
}
}
return 0;
}
Ist es in Ordnung jetzt?