在C中遇到fork(),pipe(),dup2()和exec()时遇到问题

这是我的代码:

#include  #include  #include  #include  #include  #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  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] 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 (eg, old versions of LILO) 2) booting and partitioning software from other OSs (eg, 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]) 

此外,为了更安全,您应该在执行任何closedup2操作之前处理fdPipe{STDIN_FILENO,STDOUT_FILENO} fdPipe {STDIN_FILENO,STDOUT_FILENO}重叠的情况。 如果有人设法在stdin或stdout关闭的情况下启动shell,可能会发生这种情况,并且会导致与此处的代码混淆。

编辑

  fdPipe1 fdPipe3 vv cmd1 | cmd2 | cmd3 | cmd4 | cmd5 ^ ^ fdPipe2 fdPipe4 

除了确保关闭父管道中的管道端点之外,我还试图指出fdPipe1fdPipe2不能是同一个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;

 - 管道(fdPipe);
 - 
     而(1){
          bBuffer = readline(“Shell>”);
 @@ -29,4 +27,6 @@
          } while(aPtr);

 + pipe(fdPipe);
 +
          for(i = 0; i 

这不适用于管道中的多个命令; 为此,你需要这样的东西:(未经测试,因为你必须修复其他东西)

 @@ -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;

 - 管道(fdPipe);
 - 
     而(1){
          bBuffer = readline(“Shell>”);
 @@-32,4 +30,7 @@
                  aCount = -1;

 + if(i + 1 

你应该在execvp()之后退出错误 – 它会在某个时候失败。

 exit(EXIT_FAILURE); 

正如@uncleo指出的那样,参数列表必须有一个空指针来指示结束:

 cmdArgs[aCount] = 0; 

我不清楚你让这两个程序都是自由运行的 – 看起来你需要管道中的第一个程序在开始第二个程序之前完成,如果第一个程序由于管道已满而阻塞,这不是成功的秘诀。

乔纳森有正确的想法。 你依靠第一个进程来分叉所有其他进程。 在下一个分叉之前,每个都必须运行完成。

相反,像处理一样在循环中分叉进程,但是在内部循环之外等待它们(在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 

我认为你的分叉进程将继续执行。

尝试:

  • 将其更改为“return execvp”
  • 添加’exit(1);’ 在execvp之后

一个潜在的问题是cmdargs可能在它的末尾有垃圾。 您应该在将数组传递给execvp()之前使用空指针终止该数组。

看起来grep正在接受STDIN,因此可能不会导致任何问题(尚未)。

管道中的文件描述符是引用计数,并随每个fork递增。 对于每个fork,您必须在两个描述符上发出一个close,以便将引用计数减少到零并允许管道关闭。 我正在猜测。