对Unix中管道的质疑

对Unix中管道的质疑,unix,pipe,Unix,Pipe,下面的代码用于执行ls-l | wc-l。 在代码中,如果我在parent中注释close(p[1]),那么程序将挂起,等待一些输入。为什么会这样?子级在p1上写入ls的输出,父级应该从p0获取该输出 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> main () { int

下面的代码用于执行ls-l | wc-l。 在代码中,如果我在parent中注释close(p[1]),那么程序将挂起,等待一些输入。为什么会这样?子级在p1上写入ls的输出,父级应该从p0获取该输出

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

main ()
{
  int i;
  int p[2];
  pid_t ret;
  pipe (p);
  ret = fork ();

  if (ret == 0)
    {
      close (1);
      dup (p[1]);
      close (p[0]);
      execlp ("ls", "ls", "-l", (char *) 0);
    }

  if (ret > 0)
    {
      close (0);
      dup (p[0]);
      //Doubt, Commenting the line below does not work WHy?
      close (p[1]);
      wait (NULL);
      execlp ("wc", "wc", "-l", (char *) 0);
    }
}
#包括
#包括
#包括
#包括
#包括
主要()
{
int i;
int p[2];
pid_t ret;
管道(p);
ret=fork();
如果(ret==0)
{
关闭(1);
dup(p[1]);
接近(p[0]);
execlp(“ls”、“ls”、“-l”、(char*)0);
}
如果(ret>0)
{
关闭(0);
dup(p[0]);
//怀疑,下面的评论行不起作用为什么?
关闭(p[1]);
等待(空);
execlp(“wc”、“wc”、“-l”、(char*)0);
}
}

如果子进程未关闭
p[1]
,则该FD将在两个进程中打开—父进程和子进程。父对象最终关闭它,但子对象从未关闭过,因此FD保持打开状态。因此,FD(本例中的孩子)的任何读者都将永远等待,以防万一会有更多的文章写在上面。。。不是,但读者就是不知道

pipe
+
fork
创建4个文件描述符,其中两个是输入 在fork之前,您有一个具有一个输入和一个输出的单管道

在分叉之后,您将有一个具有两个输入和两个输出的单管道

如果管道有两个输入(proc写入)和两个输出(proc读取),则需要关闭另一个输入,否则读取器也将有一个永远不会关闭的管道输入

在您的例子中,父对象是读卡器,除了管道的输出端之外,它还有一个开放的另一端,或者说是管道的输入端,理论上,这些内容可以写入管道。因此,管道从不发送eof,因为当子管道退出时,由于父管道未使用的fd,管道仍然打开

因此父级死锁,永远等待它写入自身。

注意,“
dup(p[1])
”意味着有两个文件描述符指向同一个文件。它不关闭
p[1]
;你应该明确地这样做。同样地,“
dup(p[0])
”也是如此。注意,当管道没有打开的写文件描述符时,从管道读取的文件描述符仅返回零字节(EOF);在关闭最后一个写入描述符之前,读取进程将无限期挂起。如果您
dup()
写入端,则写入端有两个打开的文件描述符,必须在读取进程获得EOF之前关闭这两个描述符

您也不需要或不希望在代码中调用
wait()。如果
ls
列表大于管道所能容纳的大小,则进程将死锁,子进程将等待
ls
完成,而
ls
将等待子进程继续读取它所写入的数据

剥离多余材料时,工作代码为:

#include <unistd.h>

int main(void)
{
    int p[2];
    pid_t ret;
    pipe(p);
    ret = fork();

    if (ret == 0)
    {
        close(1);
        dup(p[1]);
        close(p[0]);
        close(p[1]);
        execlp("ls", "ls", "-l", (char *) 0);
    } 
    else if (ret > 0)
    {
        close(0);
        dup(p[0]);
        close(p[0]);
        close(p[1]);
        execlp("wc", "wc", "-l", (char *) 0);
    }
    return(-1);
}

我明白你的回答。我还有一些疑问。1) 如何从键盘生成EOF?2) 当我们调用dup时,我们只是给文件描述符添加别名,但实际上它们在文件表中共享相同的条目。因此,如果一个进程关闭了该文件描述符的一个副本,它还能使用另一个副本吗?因为文件表中的条目必须已删除。3) 调用exec时,文件描述符是否保留在新程序中?从上面我给出的代码来看,似乎即使在调用exec之后,它仍然保留了副本。(1)终端识别一个字符(通常是control-D),并安排将所有等待的字符发送给读取程序;如果没有字符等待,则发送0字节,读取程序将0字节读取解释为EOF。(2) 是的,可以使用文件描述符的两个副本,但这通常会导致混淆。(3) 除非您将fcntl()与
FD\u CLOEXEC
选项一起使用,或者您有一个足够现代化的系统,它支持
O\u CLOEXEC
作为
open()
的标志,否则文件描述符在
exec()
操作中保持打开状态。它仍然可以访问,因为Unix经过精心设计,以确保它可以访问。您可以查看Stevens的“UNIX环境中的高级编程”或Rochkind的“高级UNIX编程”,了解如何处理文件描述符和文件表项。还有一个疑问,在上面给出的ans(@DigitalRoss)中,我是否理解正确?“当child(ls)退出时,它的描述符是关闭的,所以wc遇到EOF,因为它试图读取写入端关闭的管道。”这就是你的意思吗?我可能会将(3)简化为“是的,fd由exec保持不变”,但严格来说JL对所有事情都是正确的。我明白了。我还有一个疑问。当两个文件描述符指向同一个文件时,我对其中一个调用close()。另一个仍然可以访问吗?因为它们在文件表中共享相同的条目。因此,在Ist上调用close之后,必须删除该条目;如果有另一个文件描述符也引用文件表条目,则它不会清理该条目。只有当引用该文件表项的所有文件描述符都关闭时,才会删除该文件表项。
Black JL: gcc -Wall -Werror -Wmissing-prototypes -Wstrict-prototypes -o x x.c
Black JL: ./x
      77
Black JL: