C语言 这个管道实现有什么问题吗?

k2fxgqgv  于 11个月前  发布在  其他
关注(0)|答案(1)|浏览(104)

我想知道这个管道的实现有什么问题。我试图实现以下命令ls |grep "main-pipe" |wc。然而,它进入了无限循环,我不明白为什么它不从标准输入中读取。我还检查了在第二个过程中是否正确获得了ls的输出(对于grep)是正确的,它是正确的。我不知道是什么导致了无限循环。你能帮我吗?

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>

int main(int argc, char* argv[]) {
  if (argc > 1) {
    printf("Please don't provide additional arguments.\n");
    exit(1);
  }

  int pipe1[2];
  pipe(pipe1);

  int pid = fork();
  if (pid == 0) {
    // Write to the write end of the pipe
    close(1);
    dup(pipe1[1]);
    close(pipe1[1]);
    close(pipe1[0]);
    execlp("ls", "ls");
  }
  wait(0);

  int pipe2[2];
  pipe(pipe2);

  pid = fork();
  if (pid == 0) {
    close(0);
    dup(pipe1[0]);
    close(pipe1[0]);
    close(pipe1[1]);

    close(1);
    dup(pipe2[1]);
    close(pipe2[0]);
    close(pipe2[1]);
    execlp("grep", "grep", "main-pipe", (char *)NULL);
  }
  wait(0);

  close(0);
  dup(pipe2[0]);
  close(pipe1[0]);
  close(pipe1[1]);
  close(pipe2[0]);
  close(pipe2[1]);

  execl("wc", "wc");

  exit(0);
}

字符串

6tqwzwtp

6tqwzwtp1#

它进入了无限循环,我不明白为什么它不从标准输入中读取。
无限循环并不是进程无法执行的唯一原因。没有理由认为任何进程无法尝试从其标准输入读取数据。在这种情况下,实际上,尝试从标准输入读取数据正是阻止grep进程执行的原因。
您已经正确地执行了重定向,尽管正如我在评论中所指出的,这是dup2()dup()更好的选择。
主要问题是wait(0)调用。这些调用在语义上是不合适的,因为它们干扰了shell风格管道的一个关键特性,即参与进程并行运行,每个进程在产生输出时或多或少地消耗前一个进程的输出。然而,更重要的是,wait() s会给您带来实际问题,相当于进程间死锁。
你需要欣赏几件事:

  • 作为UNIX风格的过滤器运行的进程从其标准输入读取并处理数据,直到它检测到文件结束。通常,这样的进程将其结果发送到其标准输出,允许组合多个这样的进程。grep在从其标准输入阅读时以这种方式工作。
  • 只有在管道的写端的所有副本都被关闭之后,才能在管道的读端观察到异常。在此之前,读者永远不能确信最终不会有更多的数据要读。
  • 每个进程都有自己的文件描述符,但多个进程都可以有自己的文件描述符,引用相同的底层打开文件描述。这些FD的数值有时是相同的,但它们不需要相同。这种对同一打开文件描述的多个引用尤其是由于文件描述符在fork()中重复而发生的。

现在考虑一下这一切对您的程序的影响。
1.父节点设置一个管道,其端点存储在pipe1中。

  • 它派生了两个子进程,一个运行ls,另一个运行grep
  • 我在这里忽略了第一个wait()。如果ls的输出很长,那么这个wait()可能会导致问题,管道的缓冲区填满了容量,因此阻止了ls的完成。但这不太可能是在你的情况下实际发生的事情。
  • 每个子节点执行适当的重定向。ls重定向其标准输出到管道的写入端,而grep重定向是管道读取端的标准输入。
  • 每个子节点关闭管道两端的文件描述符的 * 自己的多余副本 *。然而,此时父节点仍然有管道两端的打开文件描述符
  • ls终止时,它所有打开的文件描述符都会自动关闭。这通常会允许grep进程在其标准输入上观察到异常,从而自行终止。然而,这不会发生在您的程序中,因为父进程仍然有一个打开的文件描述符用于该管道的写入端。
  • 同时,父节点在第二个wait()被阻塞。与前一个wait()一样,如果grep产生的输出足够大,可以填充其输出连接的管道的缓冲区,那么这个节点本身就可能是一个问题。然而,在这种情况下,这是没有意义的。因为父代推迟关闭其X1 M21 N1 X文件描述符的副本直到X1 M22 N1 X返回之后,而同时,grep进程不能正常终止,直到父进程关闭该管道的写端。一旦grep消耗了ls的所有输出,两个进程都不能继续前进--它们被死锁了。

你可以通过让父管道在派生第二个子管道时至少关闭管道1的写端来解决这个特殊的死锁,在这一点上,父管道对那个管道没有任何进一步的使用。尽快关闭不需要的管道端是一个很好的编程实践,几乎是必不可少的。但是消除wait(0)调用无论如何都是必要的,以避免我已经触及的其他问题,并且这样做将足以允许父节点足够快地执行其所有必要的管道端关闭以解决死锁。

相关问题