读取系统调用阻止共享管道

Read system call blocked sharing a pipe

我是 Unix 系统编程的新手,我正在努力理解文件描述符和管道。让我们考虑这个简单的代码:

#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>

int main() {
  int fd[2], p;
  char *m = "123456789\n", c;
  pipe(fd);
  p = fork();
  if (p == 0) {
    // child
    while(read(fd[0], &c, 1) > 0) write(1, &c, 1);
  }
  else {
    // parent
    write(fd[1], m, strlen(m));
    close(fd[1]);
    wait(NULL);
  }
  exit (0);
}

当我编译和 运行 代码时,它输出 123456789,但除非我发出 ^C,否则该过程永远不会结束。实际上,这两个进程在 htop 中都显示为已停止。

如果 child 在 read() 之前关闭 fd[1] 那么它似乎工作正常,但我不明白为什么。 fd 在两个进程之间共享,parent 在写入后关闭 fd[1]。为什么 child 在读取时没有得到 EOF?

提前致谢!

If the child closes fd[1] prior to read() then it seems to work OK but I don't understand why.

这就是您需要做的。仅此而已。从管道的读取端读取不会 return 0(发出 EOF 信号),直到内核确定不会再向该管道的写入端写入任何内容,并且只要它仍然在任何地方打开,包括读取的过程,不能确定。

嗯,首先你的 parent 进程正在等待 child 在 wait(2) 系统调用中终止,为什么你的 child 在管道中被阻塞到 read(2) 另一个字符。这两个进程都被阻止了......所以你需要从外部采取行动来取消它们。问题是 child 进程没有关闭它的管道写描述符(而且 parent 没有关闭它的管道读描述符,但这不影响这里)管道阻塞任何 reader 而至少一个这样的写描述符仍然打开。只有当所有写描述符都关闭时,读returns 0到reader.

当你执行 fork(2) 时,两个管道描述符(fd[0]fd[1])都在 child 进程中被 dup()ed,所以你有一个管道有两个打开的文件描述符(一个在 parent 中,一个在 child 中)用于写入,还有两个打开的描述符(同样,一个在 parent 中,一个在 child) 进行读取,因此当一个写入器保持管道打开以进行写入时(在本例中为 child 进程),child 进行的读取仍然会阻塞。内核无法将此检测为异常,因为如果另一个线程(或信号处理程序)需要,child 仍然可以在管道上写入。

顺便说一句,我要评论一下你在代码中做的不好的事情:

  • 首先是parent和child只考虑来自fork()的两种情况,但是如果分叉失败,它将return-1 并且你将有一个 parent 进程在没有读取进程的管道上写入,所以它可能应该阻塞(正如我所说,这不是你的情况,但这也是一个错误)你有总是检查系统调用的错误,不要假设你的 fork() 调用永远不会失败(认为 -1 被认为是 != 0 所以它落在 parent 的代码)。只有一个系统调用可以在不检查错误的情况下执行,它是close(2)(尽管对此有很多争议)
  • read()write() 也是如此。解决问题的更好方法是使用更大的缓冲区(不仅仅是一个字符,以减少程序进行的系统调用次数,从而加快速度)并使用 return 值 read() 作为 write() 调用的参数。

您的程序应该(确实在我的系统上确实如此)只需插入以下行即可工作:

close(fd[1]);

就在 child 代码中的 while 循环之前,如下所示:

#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>

int main() {
  int fd[2], p;
  char *m = "123456789\n", c;
  pipe(fd);
  p = fork();
  if (p == 0) {
    // child
    close(fd[1]);  // <--- this close is fundamental for the pipe to work properly.
    while(read(fd[0], &c, 1) > 0) write(1, &c, 1);
  }
  else if (p > 0) {
    // parent
    // another close(fd[0]); should be included here
    write(fd[1], m, strlen(m));
    close(fd[1]);
    wait(NULL);
  } else { 
    // include error processing for fork() here
  }
  exit (0);
}