Ich glaube, das Problem hier ist, dass Sie warten und sich innerhalb derselben Schleife schließen, die Kinder erzeugt. Bei der ersten Iteration wird das Kind ausgeführt (was das Kindprogramm zerstört und es mit Ihrem ersten Befehl überschreibt), und dann schließt das Elternteil alle seine Dateideskriptoren und wartet darauf, dass das Kind fertig ist, bevor es mit der Erstellung des nächsten Kinds fortfährt . Da der Elternteil an diesem Punkt alle seine Pipes geschlossen hat, haben alle weiteren Kinder nichts zum Schreiben oder Lesen. Da Sie den Erfolg Ihrer dup2-Aufrufe nicht überprüfen, bleibt dies unbemerkt.
Wenn Sie die gleiche Schleifenstruktur beibehalten möchten, müssen Sie sicherstellen, dass der übergeordnete Dateideskriptor nur die bereits verwendeten Dateideskriptoren schließt, die nicht verwendeten jedoch in Ruhe lässt. Nachdem alle untergeordneten Elemente erstellt wurden, kann Ihr übergeordnetes Element warten.
BEARBEITEN :Ich habe Eltern/Kind in meiner Antwort verwechselt, aber die Argumentation gilt immer noch:Der Prozess, der mit der Gabelung fortfährt, schließt alle seine Kopien der Pipes, sodass jeder Prozess nach der ersten Gabelung keine gültigen Dateideskriptoren zum Lesen hat an/schreiben von.
Pseudo-Code, der ein im Voraus erstelltes Array von Pipes verwendet:
/* parent creates all needed pipes at the start */
for( i = 0; i < num-pipes; i++ ){
if( pipe(pipefds + i*2) < 0 ){
perror and exit
}
}
commandc = 0
while( command ){
pid = fork()
if( pid == 0 ){
/* child gets input from the previous command,
if it's not the first command */
if( not first command ){
if( dup2(pipefds[(commandc-1)*2], 0) < ){
perror and exit
}
}
/* child outputs to next command, if it's not
the last command */
if( not last command ){
if( dup2(pipefds[commandc*2+1], 1) < 0 ){
perror and exit
}
}
close all pipe-fds
execvp
perror and exit
} else if( pid < 0 ){
perror and exit
}
cmd = cmd->next
commandc++
}
/* parent closes all of its copies at the end */
for( i = 0; i < 2 * num-pipes; i++ ){
close( pipefds[i] );
}
In diesem Code erstellt der ursprüngliche Elternprozess für jeden Befehl ein Kind und überlebt daher die gesamte Tortur. Die Kinder prüfen, ob sie ihre Eingabe vom vorherigen Befehl erhalten und ihre Ausgabe an den nächsten Befehl senden sollen. Dann schließen sie alle ihre Kopien der Pipe-Dateideskriptoren und führen dann exec aus. Der Elternteil tut nichts anderes als zu verzweigen, bis er für jeden Befehl ein Kind erzeugt hat. Es schließt dann alle seine Kopien der Deskriptoren und kann weiter warten.
Zuerst alle benötigten Pipes zu erstellen und sie dann in der Schleife zu verwalten, ist schwierig und erfordert einige Array-Arithmetik. Das Ziel sieht jedoch so aus:
cmd0 cmd1 cmd2 cmd3 cmd4
pipe0 pipe1 pipe2 pipe3
[0,1] [2,3] [4,5] [6,7]
Wenn Sie erkennen, dass Sie zu einem bestimmten Zeitpunkt nur zwei Sätze von Pipes benötigen (die Pipe zum vorherigen Befehl und die Pipe zum nächsten Befehl), wird Ihr Code vereinfacht und etwas robuster. Ephemient gibt hier Pseudo-Code dafür an. Sein Code ist sauberer, da Eltern und Kind keine unnötigen Schleifen ausführen müssen, um nicht benötigte Dateideskriptoren zu schließen, und weil die Eltern ihre Kopien der Dateideskriptoren unmittelbar nach dem Fork schließen können.
Als Randbemerkung:Sie sollten immer die Rückgabewerte von pipe, dup2, fork und exec überprüfen.
BEARBEITEN 2 :Tippfehler im Pseudocode. OP:num-pipes wäre die Anzahl der Pipes. Beispiel:"ls | grep foo | sort -r" hätte 2 Pipes.
Hier ist der korrekt funktionierende Code
void runPipedCommands(cmdLine* command, char* userInput) {
int numPipes = countPipes(userInput);
int status;
int i = 0;
pid_t pid;
int pipefds[2*numPipes];
for(i = 0; i < (numPipes); i++){
if(pipe(pipefds + i*2) < 0) {
perror("couldn't pipe");
exit(EXIT_FAILURE);
}
}
int j = 0;
while(command) {
pid = fork();
if(pid == 0) {
//if not last command
if(command->next){
if(dup2(pipefds[j + 1], 1) < 0){
perror("dup2");
exit(EXIT_FAILURE);
}
}
//if not first command&& j!= 2*numPipes
if(j != 0 ){
if(dup2(pipefds[j-2], 0) < 0){
perror(" dup2");///j-2 0 j+1 1
exit(EXIT_FAILURE);
}
}
for(i = 0; i < 2*numPipes; i++){
close(pipefds[i]);
}
if( execvp(*command->arguments, command->arguments) < 0 ){
perror(*command->arguments);
exit(EXIT_FAILURE);
}
} else if(pid < 0){
perror("error");
exit(EXIT_FAILURE);
}
command = command->next;
j+=2;
}
/**Parent closes the pipes and wait for children*/
for(i = 0; i < 2 * numPipes; i++){
close(pipefds[i]);
}
for(i = 0; i < numPipes + 1; i++)
wait(&status);
}
Der (gekürzte) relevante Code lautet:
if(fork() == 0){
// do child stuff here
....
}
else{
// do parent stuff here
if(command != NULL)
command = command->next;
j += 2;
for(i = 0; i < (numPipes ); i++){
close(pipefds[i]);
}
while(waitpid(0,0,0) < 0);
}
Das bedeutet, dass der übergeordnete (kontrollierende) Prozess dies tut:
- Gabelung
- Alle Rohre schließen
- Auf untergeordneten Prozess warten
- nächste Schleife / Kind
Aber es sollte ungefähr so aussehen:
- Gabelung
- Gabelung
- Gabelung
- schließe alle Rohre (jetzt sollte alles überspielt worden sein)
- Auf Kinder warten