2012-07-22 52 views
5

考虑下面的代码:如果一个孩子的过程在阅读时不会从写作中关闭管道,会发生什么?

int main(int argc, char *argv[]) 
{ 
    int pipefd[2]; 
    pid_t cpid; 
    char buf; 

    if (argc != 2) { 
     fprintf(stderr, "Usage: %s \n", argv[0]); 
     exit(EXIT_FAILURE); 
    } 

    if (pipe(pipefd) == -1) { 
     perror("pipe"); 
     exit(EXIT_FAILURE); 
    } 

    cpid = fork(); 
    if (cpid == -1) { 
     perror("fork"); 
     exit(EXIT_FAILURE); 
    } 

    if (cpid == 0) { /* Child reads from pipe */ 
     close(pipefd[1]);   /* Close unused write end */ 

     while (read(pipefd[0], &buf, 1) > 0) 
      write(STDOUT_FILENO, &buf, 1); 

     write(STDOUT_FILENO, "\n", 1); 
     close(pipefd[0]); 
     _exit(EXIT_SUCCESS); 

    } else {   /* Parent writes argv[1] to pipe */ 
     close(pipefd[0]);   /* Close unused read end */ 
     write(pipefd[1], argv[1], strlen(argv[1])); 
     close(pipefd[1]);   /* Reader will see EOF */ 
     wait(NULL);    /* Wait for child */ 
     exit(EXIT_SUCCESS); 
    } 
return 0; 

} 

每当子进程想要从管道中读取,它必须首先从写作关闭该管道的一侧。当我从子进程的if, 中删除该行close(pipefd[1]);时,我基本上说“好的,孩子可以从管道读取,但我允许父母同时写入管道”?

如果是这样,当管道打开时,如果读取&时会发生什么情况?没有互斥?

+0

读取()和写入()到管道保证是原子的。 (直到PIPE_BUFF大小)。这意味着:先到先得。书写/阅读部分将交错,但其边界仍然完好无损。 – wildplasser 2012-07-22 10:30:52

+2

如果您不关闭管道的孩子的写入结束,那么孩子将永远不会看到EOF,因为EOF只会在所有用户关闭写入结束时才会显示。有关示例,请参阅http://stackoverflow.com/questions/7868018/last-child-forked-will-not-die。 – ninjalj 2012-07-22 13:37:50

回答

13

每当子进程想要从管道中读取,它必须首先从写作关闭该管道的一侧。

如果进程 - 父进程或子进程不打算使用管道的写入结束,则应关闭该文件描述符。类似于管道的读取结束。系统将假定在任何进程打开写入结束时都会发生写入,即使唯一的进程是当前正在尝试从管道读取的进程,系统也不会报告EOF。此外,如果你过度填充管道,并且还有一个读取结束的进程(即使该进程是试图写入的进程),则写入将挂起,等待读者为写入完成留出空间。

当我删除该行关闭(pipefd [1]);从孩子的过程中,我基本上是说“好的,孩子可以从管道读取,但我允许父母同时写入管道”?

否;你是说孩子可以管道以及父母。任何具有管道的写入文件描述符的进程都可以写入管道。

如果是这样,当管道开放阅读和书写时会发生什么 - 不互相排斥?

有没有任何相互排斥有史以来。任何打开管道写入描述符的进程都可以随时写入管道;内核确保两个并发写操作实际上是串行化的。任何时候管道读取描述符打开的任何进程都可以从管道读取;内核确保两个并发读操作获得不同的数据字节。

通过确保只有一个进程已打开进行写入,并且只有一个进程已打开进行读取,确保管道是单向使用的。但是,这是一个编程决定。你可以有N个进程,写入结束时打开,M进程打开读取结束(并且消灭了这种想法,在N个和M个进程集之间可能有共同的进程),并且他们都能够令人惊讶地工作。但是,在写入数据后,您不能预测数据包的读取位置。

+1

很好的回答!如果可以的话+ 100! – ron 2012-07-25 09:35:00

3

fork()复制文件句柄,因此每个管道末端都有两个句柄。

现在,考虑一下。如果父母没有关闭未使用的管道末端,则仍然会有两个手柄。如果孩子死亡,孩子一方的手柄就会消失,但父母仍然拥有一个打开的手柄 - 因此,由于管道仍然完全有效,因此永远不会有“破损的管道”或“EOF”到达。没有人再把数据放入其中。

当然,也适用于其他方向。

是的,父母/孩子仍然可以使用句柄写入他们自己的管道;不过,我不记得这个用例,它仍然会给你带来同步问题。

1

当管道被创建时,它有两端读取结束和写入结束。这些是用户文件描述符表中的条目。

类似地,在读取结束和写入结束时,文件表中将有两个条目作为引用计数。当你叉,一个孩子正在创建的是描述符复制该文件,因此无论是在文件表两端的引用计数现在

变为2

现在,“当我删除该行关闭(pipefd [1])“ - >在这种情况下,即使父节点完成了写操作,该行下面的while循环将永远阻塞,以使读返回0(即EOF)。发生这种情况的原因是,即使父节点已完成写入并关闭了管道的写入端,File表中写入结束的引用计数仍为1(最初为2),因此读取函数仍在等待某些数据到达这将永远不会发生。

现在如果你还没有写“close(pipefd [0]);”在父代中,这个当前代码可能不会显示任何问题,因为您正在父代中写入一次。

但是,如果您不止一次写入,那么理想情况下您希望得到一个错误(如果孩子不再读取),但由于父项的读取结束未关闭,您将不会收到错误(即使孩子不在那里阅读)。

因此,当我们连续读取/写入数据时,未关闭未使用的端点的问题变得明显。如果我们只是读取/写入数据,这可能并不明显。

如果不是孩子的阅读循环,你只使用下面一行,你一次得到所有数据,而不是关心检查EOF,即使你的程序也能工作不写“close(pipefd [1]);”在孩子身上。

read(pipefd[0], buf, sizeof(buf));//buf is a character array sufficiently large 
+0

请注意[打开文件描述](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_253)和[打开文件描述符](http://pubs.opengroup)之间的区别。组织/ onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_166)。确实很难做到精确。计数位于打开的文件描述中,而不是描述符中。 – 2012-07-22 15:10:57

+0

@JonathanLeffler谢谢乔纳森。我在答案和“打开文件描述”中提到的“文件表条目”希望它们引用同样的内容。 – 2012-07-22 17:03:08

1

手册页管道()在SunOS: - 阅读来自空管(无缓冲数据)只有一个 结束(所有的写入文件描述符关闭)返回EOF(文件结束 )调用。

A SIGPIPE signal is generated if a write on a pipe with only 
one end is attempted. 
相关问题