Возникли проблемы с fork(), Pipe(), dup2() и exec() в C.
Вопрос
Вот мой код:
#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;
}
(Код был обновлен, чтобы отразить изменения, предложенные двумя ответами ниже, он по-прежнему не работает должным образом...)
Вот тестовый пример, в котором это не удается:
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
Проблема в том, что после этого я должен вернуться в свою оболочку и увидеть «Shell>», ожидая дальнейших входных данных.Вы также можете заметить, что не видите сообщение, похожее на «[4804] ПРЕКРАЩЕНО (Состояние:0)» (но с другим pid), что означает, что второй процесс не завершился.
Я думаю, это как-то связано с grep, потому что это работает:
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)
Вы можете легко увидеть два сообщения о завершении...
Итак, что не так с моим кодом?
Решение
Даже после выхода первой команды вашего конвейера (и, таким образом, закрытия stdout=~fdPipe[1]
), у родителя все еще есть fdPipe[1]
открыть.
Таким образом, вторая команда конвейера имеет stdin=~fdPipe[0]
он никогда не получит EOF, потому что другая конечная точка канала все еще открыта.
Вам необходимо создать новый pipe(fdPipe)
для каждого |
, и обязательно закройте обе конечные точки в родительском элементе;то есть
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])
Кроме того, для большей безопасности вам следует обработать случай fdPipe
и {STDIN_FILENO,STDOUT_FILENO}
перекрытие перед выполнением любого из close
и dup2
операции.Это может произойти, если кому-то удалось запустить вашу оболочку с закрытым стандартным вводом или выводом, и это приведет к большой путанице в коде.
Редактировать
fdPipe1 fdPipe3
v v
cmd1 | cmd2 | cmd3 | cmd4 | cmd5
^ ^
fdPipe2 fdPipe4
Помимо того, что вы закрываете конечные точки родительского канала, я пытался подчеркнуть, что fdPipe1
, fdPipe2
, и т. д. не могу быть таким же 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);
Я знаю, ты не используешь close(0)
в вашем нынешнем коде, но последний абзац предупреждает вас о необходимости следить за этим случаем.
Редактировать
Следующее минимальный изменение вашего кода заставляет его работать в конкретном случае сбоя, который вы упомянули:
@@ -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);
Это не будет работать более чем для одной команды в конвейере;для этого вам понадобится что-то вроде этого:(непроверено, так как вам придется исправлять и другие вещи)
@@ -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]); }
Другие советы
У вас должна быть ошибка выхода после execvp() — когда-нибудь произойдет сбой.
exit(EXIT_FAILURE);
Как указывает @uncleo, список аргументов должен иметь нулевой указатель, обозначающий конец:
cmdArgs[aCount] = 0;
Для меня неясно, позволяете ли вы обеим программам работать свободно - похоже, вам требуется, чтобы первая программа в конвейере завершилась перед запуском второй, что не является залогом успеха, если первая программа блокируется из-за того, что канал заполнен.
У Джонатана правильная идея.Вы полагаетесь на первый процесс, чтобы разветвить все остальные.Каждый из них должен быть завершен до завершения, прежде чем будет разветвлен следующий.
Вместо этого создайте цикл процессов, как вы это делаете, но дождитесь их вне внутреннего цикла (внизу большого цикла для приглашения оболочки).
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
Я думаю, что ваши раздвоенные процессы продолжат выполняться.
Попробуйте либо:
- Меняем его на return execvp
- Добавить 'Exit (1);' после execvp
Одна из потенциальных проблем заключается в том, что в конце cmdargs может быть мусор.Вы должны завершить этот массив нулевым указателем перед передачей его в execvp().
Однако похоже, что grep принимает STDIN, так что это может не вызывать никаких проблем (пока).
дескрипторы файлов из канала подсчитываются ссылками и увеличиваются с каждым разветвлением.для каждого разветвления вам необходимо выполнить закрытие обоих дескрипторов, чтобы уменьшить счетчик ссылок до нуля и позволить каналу закрыться.Я догадываюсь.