C语言中的dup2、pipe和fork无法按预期工作

7
我想要做一个简单的fork操作 -> 执行另一个程序 -> 对子进程说“hello” -> 读取返回值 -> 打印接收到的内容。作为子进程使用的程序只是等待任何输入行并将类似“hello there!”的内容打印到stdout。
这是我的“主机”程序(无法工作):
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

#define IN 0
#define OUT 1
#define CHILD 0

main ()
{
  pid_t pid;
  int pipefd[2];
  FILE* output;
  char buf[256];

  pipe(pipefd);

  pid = fork();    
  if (pid == CHILD)
  {
    printf("child\n");
    dup2(pipefd[IN], IN);
    dup2(pipefd[OUT], OUT);
    execl("./test", "test", (char*) NULL);
  }
  else
  {
    sleep(1);
    printf("parent\n");
    write(pipefd[IN], "hello!", 10); // write message to the process    
    read(pipefd[OUT], buf, sizeof(buf));
    printf("received: %s\n", buf);
  }
}

I get this:

child
[.. waits 1 second ..]
parent
received: 

我漏掉了什么?谢谢!

编辑 (test.c):

按要求,这是子程序:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int getln(char line[])
{
  int nch = 0;
  int c;

  while((c = getchar()) != EOF)
    {
      if(c == '\n') break;
    line[nch] = c;
    nch++;
    }

 if(c == EOF && nch == 0) return EOF;
 return nch;
}

main()
{
  char line[20];

  getln(line);
  printf("hello there!", line);
  fflush(stdout);  
  return 0;
}
6个回答

4
您应该总是从文件描述符0读取,并使用管道写入文件描述符1...您在父进程中颠倒了这种关系。对于您想要做的事情,您可能需要两个管道进行父子之间的双向通信,以避免出现父进程读取其写入管道的内容的情况,因为进程调度是不确定的(即,如果父进程也从同一管道中读取,那么子进程不保证读取父进程写入管道的内容,因为父进程可能只是写入然后读取,而没有交替子进程读取父进程写入的内容)。

将您的代码更改为以下内容:

main ()
{
  pid_t pid;
  int pipe_to_child[2];
  int pipe_from_child[2];
  FILE* output;
  char buf[256];

  pipe(pipe_to_child);
  pipe(pipe_from_child);

  pid = fork();    
  if (pid == CHILD)
  {
    printf("child\n");

    //child process not using these ends of the pipe, so close them
    close(pipe_to_child[1]);
    close(pipe_from_child[0]);

    dup2(pipe_to_child[0], fileno(stdin));
    dup2(pipe_from_child[1], fileno(stdout));

    execl("./test", "test", (char*) NULL);
  }
  else
  {
    sleep(1);
    printf("parent\n");
    write(pipe_to_child[1], "hello!\n", 10); // write message to the process    
    read(pipe_from_child[0], buf, sizeof(buf));
    printf("received: %s\n", buf);
  }
}

2

您需要两个管道:一个用于子进程的标准输入,一个用于其标准输出。您不能将一个管道的两个端点重复使用作为两个管道。

此外,父进程程序的这行代码

write(pipefd[IN], "hello!", 10); // write message to the process    

这段代码没有写入换行符,所以子进程中的getln永远不会返回。(此外,“hello!”只有六个字符,但是你却写了十个字符。)


1

其他人说管道是单向的,这也是我最初的想法。但实际上我的手册并没有这么说:

 A read from fildes[0] accesses the data written to fildes[1]
 on  a  first-in-first-out  (FIFO)  basis  and  a  read  from
 fildes[1] accesses the data written to fildes[0] also  on  a
 FIFO basis.

然而,这意味着如果父进程正在写入pipefd [0],那么子进程应该从pipefd [1]读取,因此您将子进程的标准输入和输出与管道的错误侧面关联。

从手册页面上看,似乎可以使用一个管道来完成此操作。但是,使用两个管道可能会使代码更清晰易懂。

您似乎认为pipefd的每个元素都是单独的管道,但实际情况并非如此。


根据 POSIX 规范中关于 pipe 的说明,"未指定 fildes[0] 是否也打开了写入权限,以及 fildes[1] 是否也打开了读取权限。" - Jason
是的,如果您想要一个保证的双向通道,您需要使用 socketpair 或者伪终端。 - zwol

1

你可能应该使用 waitwaitpid


1

看起来你的管道描述符混淆了。在调用pipe()之后,pipefd[0]是管道的读取端,而pipefd[1]是管道的写入端。你正在向读取端写入数据,并从写入端读取数据。

此外,你试图使用一个管道来同时处理子进程的标准输入和标准输出。我不认为这是你真正想做的事情(你需要两个管道)。


1
看起来你使用管道的 IN/OUT 相反了 -- pipefd[0] 是管道的读取端,所以向其写入内容(父进程在此这样做)是没有意义且会失败。同样地,pipefd[1] 是管道的写入端,所以从中读取内容(父进程在此这样做)也将导致失败。你应该始终检查读取和写入调用的返回值,以查看是否发生任何错误。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接