C에서 포크(), 파이프(), dup2() 및 exec()에 문제가 있습니다.
문제
내 코드는 다음과 같습니다.
#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] TERMINATED(상태: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
운영. 누군가가 stdin 또는 stdout을 닫은 상태에서 당신의 쉘을 시작한 경우, 여기에서 코드와 큰 혼란을 초래할 수 있습니다.
편집하다
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'로 변경
- '종료 (1);'추가 execvp 이후
잠재적 인 문제 중 하나는 CMDARG가 끝에 쓰레기를 가질 수 있다는 것입니다. execvp ()에 전달하기 전에 널 포인터가있는 배열을 종료해야합니다.
그러나 Grep이 Stdin을 수락하는 것처럼 보이므로 아직 문제가 발생하지 않을 수 있습니다.
파이프의 파일 디스크립터는 참조 계산되고 각 포크로 증가합니다. 모든 포크의 경우 기준 수를 0으로 줄이고 파이프를 닫으려면 두 설명자에 닫기를 발행해야합니다. 나는 추측하고있다.