Des problèmes avec fork (), le tuyau (), dup2 () et exec () en C
Question
Voici mon code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
#include <readline/readline.h>
#define NUMPIPES 2
int main(int argc, char *argv[]) {
char *bBuffer, *sPtr, *aPtr = NULL, *pipeComms[NUMPIPES], *cmdArgs[10];
int fdPipe[2], pCount, aCount, i, status, lPids[NUMPIPES];
pid_t pid;
pipe(fdPipe);
while(1) {
bBuffer = readline("Shell> ");
if(!strcasecmp(bBuffer, "exit")) {
return 0;
}
sPtr = bBuffer;
pCount = -1;
do {
aPtr = strsep(&sPtr, "|");
pipeComms[++pCount] = aPtr;
} while(aPtr);
for(i = 0; i < pCount; i++) {
aCount = -1;
do {
aPtr = strsep(&pipeComms[i], " ");
cmdArgs[++aCount] = aPtr;
} while(aPtr);
cmdArgs[aCount] = 0;
if(strlen(cmdArgs[0]) > 0) {
pid = fork();
if(pid == 0) {
if(i == 0) {
close(fdPipe[0]);
dup2(fdPipe[1], STDOUT_FILENO);
close(fdPipe[1]);
} else if(i == 1) {
close(fdPipe[1]);
dup2(fdPipe[0], STDIN_FILENO);
close(fdPipe[0]);
}
execvp(cmdArgs[0], cmdArgs);
exit(1);
} else {
lPids[i] = pid;
/*waitpid(pid, &status, 0);
if(WIFEXITED(status)) {
printf("[%d] TERMINATED (Status: %d)\n",
pid, WEXITSTATUS(status));
}*/
}
}
}
for(i = 0; i < pCount; i++) {
waitpid(lPids[i], &status, 0);
if(WIFEXITED(status)) {
printf("[%d] TERMINATED (Status: %d)\n",
lPids[i], WEXITSTATUS(status));
}
}
}
return 0;
}
(Le code a été mis à jour pour refléter les changements proposés par il deux réponses ci-dessous, il ne fonctionne toujours pas comme il se doit ...)
Voici le cas de test où cela échoue:
nazgulled ~/Projects/SO/G08 $ ls -l
total 8
-rwxr-xr-x 1 nazgulled nazgulled 7181 2009-05-27 17:44 a.out
-rwxr-xr-x 1 nazgulled nazgulled 754 2009-05-27 01:42 data.h
-rwxr-xr-x 1 nazgulled nazgulled 1305 2009-05-27 17:50 main.c
-rwxr-xr-x 1 nazgulled nazgulled 320 2009-05-27 01:42 makefile
-rwxr-xr-x 1 nazgulled nazgulled 14408 2009-05-27 17:21 prog
-rwxr-xr-x 1 nazgulled nazgulled 9276 2009-05-27 17:21 prog.c
-rwxr-xr-x 1 nazgulled nazgulled 10496 2009-05-27 17:21 prog.o
-rwxr-xr-x 1 nazgulled nazgulled 16 2009-05-27 17:19 test
nazgulled ~/Projects/SO/G08 $ ./a.out
Shell> ls -l|grep prog
[4804] TERMINATED (Status: 0)
-rwxr-xr-x 1 nazgulled nazgulled 14408 2009-05-27 17:21 prog
-rwxr-xr-x 1 nazgulled nazgulled 9276 2009-05-27 17:21 prog.c
-rwxr-xr-x 1 nazgulled nazgulled 10496 2009-05-27 17:21 prog.o
Le problème est que je revenir à ma coquille après cela, je devrais voir « Shell> » en attente pour plus d'entrée. Vous pouvez également remarquer que vous ne voyez pas un message similaire à « [4804] TERMINÉ (Statut: 0) ». (Mais avec un autre pid), ce qui signifie que le second processus n'a pas mis fin
Je pense qu'il a quelque chose à voir avec grep, parce que cela fonctionne:
nazgulled ~/Projects/SO/G08 $ ./a.out
Shell> echo q|sudo fdisk /dev/sda
[4838] TERMINATED (Status: 0)
The number of cylinders for this disk is set to 1305.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
(e.g., DOS FDISK, OS/2 FDISK)
Command (m for help):
[4839] TERMINATED (Status: 0)
Vous pouvez facilement voir deux messages "terminate" ...
Alors, quel est le problème avec mon code?
La solution
Même après la première commande de vos sorties de pipeline (et Thust ferme stdout=~fdPipe[1]
), le
fdPipe[1]
ouvert.
Ainsi, la deuxième commande du pipeline a une stdin=~fdPipe[0]
qui ne reçoit un EOF, parce que l'autre extrémité de la conduite est toujours ouverte.
Vous devez créer une nouvelle pipe(fdPipe)
pour chaque |
, et assurez-vous de fermer les deux extrémités du parent; i.e..
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
if there are multiple cmds
close(old_fds[0])
close(old_fds[1])
En outre, pour être plus sûr, vous devez gérer le cas de chevauchement de fdPipe
et {STDIN_FILENO,STDOUT_FILENO}
avant d'effectuer l'une des opérations de close
et dup2
. Cela peut se produire si quelqu'un a réussi à démarrer votre shell avec stdin ou stdout fermé, et se traduira par une grande confusion avec le code ici.
Modifier
fdPipe1 fdPipe3
v v
cmd1 | cmd2 | cmd3 | cmd4 | cmd5
^ ^
fdPipe2 fdPipe4
En plus de vous que vous fermez les points d'extrémité du tuyau dans le parent, je tentais de faire valoir que fdPipe1
, fdPipe2
, etc. ne peuvent pas la même pipe()
.
/* suppose stdin and stdout have been closed...
* for example, if your program was started with "./a.out <&- >&-" */
close(0), close(1);
/* then the result you get back from pipe() is {0, 1} or {1, 0}, since
* fd numbers are always allocated from the lowest available */
pipe(fdPipe);
close(0);
dup2(fdPipe[0], 0);
Je sais que vous n'utilisez pas close(0)
dans votre code actuel, mais le dernier paragraphe vous avertit de regarder pour ce cas.
Modifier
les minimum changement à votre code fait fonctionner dans le cas particulier d'échec que vous avez mentionné:
@@ -12,6 +12,4 @@ pid_t pid; - pipe(fdPipe); - while(1) { bBuffer = readline("Shell> "); @@ -29,4 +27,6 @@ } while(aPtr); + pipe(fdPipe); + for(i = 0; i < pCount; i++) { aCount = -1; @@ -72,4 +72,7 @@ } + close(fdPipe[0]); + close(fdPipe[1]); + for(i = 0; i < pCount; i++) { waitpid(lPids[i], &status, 0);
Cela ne fonctionnera pas pendant plus d'une commande dans le pipeline; pour cela, vous avez besoin de quelque chose comme ceci: (non testé, comme vous devez fixer d'autres choses aussi)
@@ -9,9 +9,7 @@ int main(int argc, char *argv[]) { char *bBuffer, *sPtr, *aPtr = NULL, *pipeComms[NUMPIPES], *cmdArgs[10]; - int fdPipe[2], pCount, aCount, i, status, lPids[NUMPIPES]; + int fdPipe[2], fdPipe2[2], pCount, aCount, i, status, lPids[NUMPIPES]; pid_t pid; - pipe(fdPipe); - while(1) { bBuffer = readline("Shell> "); @@ -32,4 +30,7 @@ aCount = -1; + if (i + 1 < pCount) + pipe(fdPipe2); + do { aPtr = strsep(&pipeComms[i], " "); @@ -43,11 +44,12 @@ if(pid == 0) { - if(i == 0) { - close(fdPipe[0]); + if(i + 1 < pCount) { + close(fdPipe2[0]); - dup2(fdPipe[1], STDOUT_FILENO); + dup2(fdPipe2[1], STDOUT_FILENO); - close(fdPipe[1]); - } else if(i == 1) { + close(fdPipe2[1]); + } + if(i != 0) { close(fdPipe[1]); @@ -70,4 +72,17 @@ } } + + if (i != 0) { + close(fdPipe[0]); + close(fdPipe[1]); + } + + fdPipe[0] = fdPipe2[0]; + fdPipe[1] = fdPipe2[1]; + } + + if (pCount) { + close(fdPipe[0]); + close(fdPipe[1]); }
Autres conseils
Vous devriez avoir une sortie d'erreur après execvp () -. Il échouera quelque temps
exit(EXIT_FAILURE);
Comme @uncleo souligne, la liste des arguments doit avoir un pointeur NULL pour indiquer la fin:
cmdArgs[aCount] = 0;
Il est clair pour moi que vous laissez les deux programmes sont exécutés gratuitement - il semble que vous avez besoin du premier programme dans le pipeline pour terminer avant de commencer la seconde, ce qui est une recette pour le succès si les premiers blocs de programme parce que le tuyau est plein.
Jonathan a la bonne idée. Vous comptez sur le premier processus de fourche tous les autres. Chacun doit se terminer avant le prochain est fourchue.
Au lieu de cela, la fourchette des processus dans une boucle comme vous faites, mais attendez pour eux en dehors de la boucle intérieure, (au fond de la grande boucle pour l'invite du shell).
loop //for prompt
next prompt
loop //to fork tasks, store the pids
if pid == 0 run command
else store the pid
end loop
loop // on pids
wait
end loop
end loop
Je pense que vos processus fourchues continuera à exécuter.
Essayez soit:
- Modification à 'execvp retour'
- Ajouter 'sortie (1);' après execvp
Un problème potentiel est que cmdargs peut avoir des ordures à la fin de celui-ci. Vous êtes censé mettre fin à ce tableau avec un pointeur NULL avant de passer à execvp ().
On dirait que grep est d'accepter STDIN, cependant, de sorte que pourrait ne pas être à l'origine des problèmes (encore).
les descripteurs de fichiers à partir du tuyau sont comptées référence, et incrémenté à chaque fourchette. pour chaque fourchette, vous devez émettre un proche des deux descripteurs afin de réduire le nombre de références à zéro et laisser le tuyau pour fermer. Je devine.