Je crois que le problème ici est que vous attendez et que vous vous refermez dans la même boucle qui crée des enfants. Lors de la première itération, l'enfant exécutera (ce qui détruira le programme enfant en l'écrasant avec votre première commande), puis le parent fermera tous ses descripteurs de fichier et attendra que l'enfant se termine avant de passer à la création de l'enfant suivant. . À ce stade, puisque le parent a fermé tous ses canaux, tous les autres enfants n'auront plus rien à écrire ou à lire. Puisque vous ne vérifiez pas le succès de vos appels dup2, cela passe inaperçu.
Si vous souhaitez conserver la même structure de boucle, vous devez vous assurer que le parent ne ferme que les descripteurs de fichier qui ont déjà été utilisés, mais laisse seuls ceux qui ne l'ont pas été. Ensuite, une fois que tous les enfants ont été créés, votre parent peut attendre.
MODIFIER :J'ai mélangé le parent/enfant dans ma réponse, mais le raisonnement tient toujours :le processus qui continue à bifurquer ferme à nouveau toutes ses copies des canaux, donc tout processus après le premier bifurcation n'aura pas de descripteurs de fichiers valides à lire à/écrire à partir de.
pseudo-code, utilisant un tableau de canaux créés à l'avance :
/* 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] );
}
Dans ce code, le processus parent d'origine crée un enfant pour chaque commande et survit donc à toute l'épreuve. Les enfants vérifient s'ils doivent obtenir leur entrée de la commande précédente et s'ils doivent envoyer leur sortie à la commande suivante. Ensuite, ils ferment toutes leurs copies des descripteurs de fichier pipe, puis exec. Le parent ne fait rien d'autre que bifurquer jusqu'à ce qu'il ait créé un enfant pour chaque commande. Il ferme alors toutes ses copies des descripteurs et peut continuer à attendre.
Créer d'abord tous les canaux dont vous avez besoin, puis les gérer dans la boucle, est délicat et nécessite une certaine arithmétique de tableau. L'objectif, cependant, ressemble à ceci :
cmd0 cmd1 cmd2 cmd3 cmd4
pipe0 pipe1 pipe2 pipe3
[0,1] [2,3] [4,5] [6,7]
Réaliser que, à un moment donné, vous n'avez besoin que de deux ensembles de canaux (le canal vers la commande précédente et le canal vers la commande suivante) simplifiera votre code et le rendra un peu plus robuste. Ephemient donne un pseudo-code pour cela ici. Son code est plus propre, car le parent et l'enfant n'ont pas à faire de boucle inutile pour fermer les descripteurs de fichiers inutiles et parce que le parent peut facilement fermer ses copies des descripteurs de fichiers immédiatement après le fork.
En remarque :vous devez toujours vérifier les valeurs de retour de pipe, dup2, fork et exec.
MODIFICATION 2 :faute de frappe dans le pseudo-code. OP :num-pipes serait le nombre de tubes. Par exemple, "ls | grep foo | sort -r" aurait 2 canaux.
Voici le bon code de fonctionnement
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);
}
Le code correspondant (abrégé) est :
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);
}
Ce qui signifie que le processus parent (contrôle) fait ceci :
- fourche
- fermer tous les tuyaux
- attendre le processus enfant
- boucle suivante / enfant
Mais cela devrait ressembler à ceci :
- fourche
- fourche
- fourche
- fermez tous les tuyaux (tout aurait dû être dupé maintenant)
- attendre les enfants