Probleme mit fork (), Rohr (), dup2 () und exec () in C
Frage
Hier ist mein 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;
}
(Der Code wurde aktualisiert er, um Änderungen von zwei Antworten vorgeschlagen unten, ist es noch nicht, wie es sollte funktioniert ...)
Hier ist der Testfall, wenn dies fehlschlägt:
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
Das Problem ist, dass ich meine Shell danach zurückkehren sollte, sollte ich sehen „Shell>“ für weitere Eingabe wartet. Sie können auch feststellen, dass Sie keine Meldung ähnlich „[4804] BEENDET (Status: 0)“ sehen. (Aber mit einem anderen pid), die den zweiten Prozess bedeutet nicht beenden
Ich denke, es hat etwas mit grep zu tun, denn das funktioniert:
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)
Sie können ganz einfach zwei "beenden" Nachrichten sehen ...
Also, was ist los mit meinem Code?
Lösung
Auch nach dem ersten Befehl des Pipeline-Exits (und Thust schließt stdout=~fdPipe[1]
) haben die Eltern noch fdPipe[1]
offen.
So hat der zweite Befehl der Pipeline eine stdin=~fdPipe[0]
, die nie ein EOF bekommt, weil der andere Endpunkt des Rohres noch offen ist.
Sie müssen eine neue pipe(fdPipe)
für jeden |
erstellen, und stellen Sie sicher, beiden Endpunkte in den Eltern zu schließen; d.
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])
Auch als sicherer, sollten Sie den Fall von fdPipe
und {STDIN_FILENO,STDOUT_FILENO}
überlappende behandeln, bevor eine der close
und dup2
Operationen. Dies kann passieren, wenn jemand Ihre Schale mit stdin oder stdout geschlossen zu starten geschafft hat, und hier in großer Verwirrung mit dem Code führen wird.
Bearbeiten
fdPipe1 fdPipe3
v v
cmd1 | cmd2 | cmd3 | cmd4 | cmd5
^ ^
fdPipe2 fdPipe4
Neben dafür, dass Sie das Rohr Endpunkte in der Mutter schließen, ich hatte versucht, den Punkt zu machen, die fdPipe1
, fdPipe2
usw. nicht die gleiche seine 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);
Ich weiß, dass Sie nicht close(0)
in Ihrer aktuellen Code nicht verwenden, aber der letzte Absatz warnt Sie für diesen Fall zu achten.
Bearbeiten
Das folgende minimal Änderung an Ihrem Code macht es im konkreten Fall andernfalls arbeiten Sie erwähnt:
@@ -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);
Dies wird nicht für mehr als einen Befehl in der Pipeline arbeiten; für dass, würden Sie so etwas wie dies benötigen: (ungetestet, wie Sie auch andere Dinge zu beheben haben)
@@ -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]); }
Andere Tipps
Sie sollten einen Fehler Ausfahrt nach execvp haben (.) - es wird irgendwann scheitern
exit(EXIT_FAILURE);
Wie @uncleo weist darauf hin, muss die Argumentliste hat einen Null-Zeiger das Ende, um anzuzeigen:
cmdArgs[aCount] = 0;
Es ist mir nicht klar, dass Sie beide Programme frei laufen lassen - es scheint, dass Sie das erste Programm in der Pipeline erfordern vor dem Start des zweiten zu beenden, die, wenn die ersten Programmblöcke, weil das Rohr kein Rezept für Erfolg voll ist.
Jonathan hat die richtige Idee. Sie stützen sich auf den ersten Prozess alle anderen gabeln. Jeder hat zu Ende laufen, bevor die nächsten gegabelt ist.
Stattdessen gabeln die Prozesse in einer Schleife wie Sie tun, aber für sie außerhalb der inneren Schleife warten, (am unteren Rand der großen Schleife für die Shell-Eingabeaufforderung).
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
Ich denke, Ihre gegabelt Prozesse Ausführung wird fortgesetzt.
Versuchen Sie entweder:
- Ändern Sie ihn auf 'Rückkehr execvp'
- Add 'exit (1);' nach execvp
Ein mögliches Problem ist, dass cmdargs Müll am Ende davon haben kann. Sie sollten das Array mit einem Null-Zeiger beenden, bevor er es execvp ().
Es sieht aus wie grep ist STDIN zu akzeptieren, obwohl, so dass möglicherweise keine Probleme verursacht (noch) nicht.
Die Datei-Deskriptoren von dem Rohr sind Referenz gezählt und mit jeder Gabel inkrementiert. für jede Gabel, haben Sie eine enge auf beiden Deskriptoren auszugeben, um den Referenzzähler auf Null und damit das Rohr zu schließen, zu reduzieren. Ich nehme an.