Problemi con la forcella (), pipe (), dup2 () e exec () in C
Domanda
Ecco il mio codice:
#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;
}
(Il codice è stato aggiornato per riflettere che le modifiche proposte da due risposte qui sotto, che ancora non funziona come dovrebbe ...)
Ecco il banco di prova in cui questo non riesce:
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
Il problema è che io tornassi al mio shell dopo che, dovrei vedere "Shell>" in attesa di più input. Si può anche notare che non si vede un messaggio simile a "[4804] TERMINATO (stato: 0)". (Ma con un diverso PID), il che significa che il secondo processo non terminerà
Credo che abbia qualcosa a che fare con grep, perché questo funziona:
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)
Si può facilmente vedere due messaggi "terminare" ...
Quindi, cosa c'è di sbagliato con il mio codice?
Soluzione
Anche dopo il primo comando delle vostre uscite di tubazioni (e Thust chiude stdout=~fdPipe[1]
), il genitore ha ancora fdPipe[1]
aperta.
Così, il secondo comando della tubazione ha un stdin=~fdPipe[0]
che non viene mai un EOF, perché l'altro estremo del tubo è ancora aperto.
È necessario creare un nuovo pipe(fdPipe)
per ogni |
, e assicurarsi di chiudere entrambi gli endpoint nel genitore; cioè.
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])
Inoltre, per essere più sicuro, si dovrebbe gestire il caso di fdPipe
e {STDIN_FILENO,STDOUT_FILENO}
sovrapposizione prima di eseguire una qualsiasi delle operazioni close
e dup2
. Ciò può accadere se qualcuno è riuscito ad avviare la shell con stdin o stdout chiuse, e si tradurrà in una grande confusione con il codice qui.
Modifica
fdPipe1 fdPipe3
v v
cmd1 | cmd2 | cmd3 | cmd4 | cmd5
^ ^
fdPipe2 fdPipe4
Oltre a fare in modo di chiudere gli endpoint del tubo nel genitore, stavo cercando di fare il punto che fdPipe1
, fdPipe2
, ecc non può essere lo stesso 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);
Lo so che non si utilizza close(0)
nel codice attuale, ma l'ultimo paragrafo è allarme di guardare fuori per questo caso.
Modifica
Il seguente minima modifica al codice lo fa funzionare, nel caso specifico difetto lei ha citato:
@@ -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);
Questo non funziona per più di un comando nella pipeline; per questo, avresti bisogno di qualcosa di simile: (non testato, come si deve risolvere anche altre cose)
@@ -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]); }
Altri suggerimenti
Si dovrebbe avere un'uscita di errore dopo execvp () -. Fallirà qualche tempo
exit(EXIT_FAILURE);
Come @uncleo sottolinea, la lista degli argomenti deve avere un puntatore nullo per indicare la fine:
cmdArgs[aCount] = 0;
Non è chiaro per me che si lascia che entrambi i programmi correre liberi - sembra che si richiede il primo programma in cantiere per finire prima di iniziare la seconda, che non è una ricetta per il successo se i primi blocchi di programma perché il tubo è pieno.
Jonathan ha l'idea giusta. Ci si affida il primo processo di sborsare tutti gli altri. Ognuno deve correre a compimento prima della successiva è biforcuta.
Invece, sborsare i processi in un ciclo, come si sta facendo, ma attendere per loro al di fuori del ciclo interno, (in fondo il grande anello per il prompt della 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
Credo che i processi biforcuta continuerà l'esecuzione.
Prova uno:
- Cambiare a 'execvp ritorno'
- Aggiungi 'exit (1);' dopo execvp
Un potenziale problema è che cmdargs possono avere spazzatura, alla fine di esso. Dovresti porre termine a tale matrice con un puntatore nullo prima di passarlo a execvp ().
Sembra che grep sta accettando STDIN, però, in modo che non potrebbe essere la causa dei problemi (ancora).
i descrittori di file dal tubo vengono contati riferimento, e incrementate con ogni forcella. per ogni forcella, è necessario emettere una stretta su entrambi i descrittori per ridurre il conteggio di riferimento a zero e consentire al tubo di chiudere. Sto indovinando.