在 C 中使用 fork()、pipe()、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 之后,我应该看到“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)
你可以很容易地看到两条“终止”消息......
那么,我的代码有什么问题吗?
解决方案
即使你的管道出口(和thust关闭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
重叠的情况下。如果有人已经设法开始与标准输入和标准输出关闭你的shell,这可能发生,将导致这里的代码很大的混乱。
修改
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
我认为您的分叉进程将继续执行。
尝试以下任一方法:
- 将其更改为“返回 execvp”
- 添加“退出(1);” execvp之后
的一个潜在问题是,cmdargs可以在它的端部有垃圾。你应该终止与一个空指针数组将其传递给execvp()之前。
它看起来像的grep正在接受STDIN,虽然如此,这可能不造成任何问题(还)。
从管道的文件描述符引用计数,以及与每个叉递增。每一个叉,你必须发出,以减少引用计数为零,并允许关闭管道上都描述了帷幕。我猜测。