Credo che il problema qui sia che tu aspetti e ti chiudi all'interno dello stesso ciclo che sta creando i bambini. Alla prima iterazione, il figlio verrà eseguito (che distruggerà il programma figlio, sovrascrivendolo con il tuo primo comando) e quindi il genitore chiude tutti i suoi descrittori di file e attende che il figlio finisca prima di iterare per creare il figlio successivo . A quel punto, poiché il genitore ha chiuso tutte le sue pipe, gli altri figli non avranno nulla su cui scrivere o da cui leggere. Dal momento che non stai verificando il successo delle tue chiamate dup2, questo passerà inosservato.
Se vuoi mantenere la stessa struttura del ciclo, dovrai assicurarti che il genitore chiuda solo i descrittori di file che sono già stati usati, ma lasci quelli che non lo sono stati da soli. Quindi, dopo che tutti i figli sono stati creati, il tuo genitore può aspettare.
MODIFICA :ho confuso genitore/figlio nella mia risposta, ma il ragionamento è ancora valido:il processo che prosegue con il fork chiude nuovamente tutte le sue copie delle pipe, quindi qualsiasi processo dopo il primo fork non avrà descrittori di file validi da leggere da/scrivere da.
pseudo codice, utilizzando un array di pipe creato in anticipo:
/* 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 questo codice, il processo genitore originale crea un figlio per ogni comando e quindi sopravvive all'intero calvario. I bambini controllano se devono ricevere il loro input dal comando precedente e se devono inviare il loro output al comando successivo. Quindi chiudono tutte le loro copie dei descrittori di file pipe e quindi exec. Il genitore non fa altro che eseguire il fork fino a quando non viene creato un figlio per ogni comando. Quindi chiude tutte le sue copie dei descrittori e può continuare ad attendere.
Creare prima tutte le pipe di cui hai bisogno e poi gestirle nel ciclo è complicato e richiede un po 'di aritmetica dell'array. L'obiettivo, tuttavia, è simile a questo:
cmd0 cmd1 cmd2 cmd3 cmd4
pipe0 pipe1 pipe2 pipe3
[0,1] [2,3] [4,5] [6,7]
Comprendere che, in un dato momento, hai solo bisogno di due serie di pipe (la pipe per il comando precedente e la pipe per il comando successivo) semplificherà il tuo codice e lo renderà un po' più robusto. Ephemient fornisce uno pseudo-codice per questo qui. Il suo codice è più pulito, perché il genitore e il figlio non devono eseguire cicli inutili per chiudere i descrittori di file non necessari e perché il genitore può facilmente chiudere le sue copie dei descrittori di file immediatamente dopo il fork.
Come nota a margine:dovresti sempre controllare i valori restituiti da pipe, dup2, fork ed exec.
MODIFICA 2 :errore di battitura in pseudo codice. OP:num-pipe sarebbe il numero di pipe. Ad esempio, "ls | grep foo | sort -r" avrebbe 2 pipe.
Ecco il codice di funzionamento corretto
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);
}
Il codice pertinente (abbreviato) è:
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);
}
Il che significa che il processo padre (di controllo) fa questo:
- forchetta
- chiudi tutti i tubi
- attendere il processo figlio
- ciclo successivo / figlio
Ma dovrebbe essere qualcosa del genere:
- forchetta
- forchetta
- forchetta
- chiudi tutte le pipe (tutto dovrebbe essere stato ingannato ora)
- aspetta i bambini