这种管道实现有什么问题?

问题描述 投票:0回答:1

我想知道这个管道的实现有什么问题。我正在尝试执行以下命令

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);
}
c unix process operating-system posix
1个回答
0
投票

它进入无限循环,我不明白为什么它不从标准输入读取。

无限循环并不是无法取得进展的唯一原因。没有理由认为任何进程都无法尝试读取其标准输入。事实上,在这种情况下,尝试从标准输入读取正是阻止

grep
进程取得进展的原因。

您已经正确执行了重定向,尽管正如我在评论中指出的那样,这是许多情况下

dup2()
dup()
更好的选择。

主要问题是您的

wait(0)
电话。这些在语义上是不合适的,因为它们干扰了 shell 式管道的关键特征之一,即参与进程并行运行,每个进程在生成时或多或少地消耗前一个进程的输出。然而,更重要的是,
wait()
会给您带来一个实际问题,相当于进程间死锁。

你需要欣赏几件事:

  • 作为 UNIX 样式过滤器运行的进程从其标准输入读取并处理数据,直到检测到文件结尾。通常,这样的进程将其结果发送到其标准输出,从而允许组合多个这样的进程。

    grep
    从标准输入读取时以这种方式工作。

  • 仅当管道写入端的所有副本都已关闭后,才会在管道的读取端观察到 EOF。在那之前,读者永远不能确信最终不会有更多数据可供阅读。
  • 每个进程都有自己的文件描述符,但多个进程都可以有自己的文件描述符,这些文件描述符引用相同的底层打开文件描述。这些 FD 的数值有时相同,但并非必须如此。对同一打开文件描述的多次引用尤其是由于文件描述符在
  • fork()

    中重复而发生。

    
    

  • 现在考虑一下这一切对您的程序有何影响。

    父级设置了一个管道,其末端存储在
  1. pipe1

    中。

    
    

  2. 它分叉了两个孩子,一个运行
  3. ls

    ,另一个运行

    grep
    。其中每个都继承了管道末端的父级文件描述符的副本。
    
    

    我在这里忽略第一个
      wait()
    • 。如果
      ls
      的输出很长,那么这个
      wait()
      可能会导致问题,管道的缓冲区已满,从而阻止
      ls
      完成。但这不太可能是您的案例中实际发生的情况。
      
      
  4. 每个孩子都会执行适当的重定向。
  5. ls

    将其标准输出重定向到管道的写入端,而

    grep
    重定向是来自管道的读取端的标准输入。
    
    

  6. 每个子进程都会关闭该管道末端的文件描述符的
  7. 自己的多余副本。然而,此时,

    父管道的两端仍然有打开的文件描述符

  8. ls
  9. 终止时,其所有打开的文件描述符都会自动关闭。这通常允许

    grep

     进程观察其标准输入上的 EOF,从而自行终止。然而,这在您的程序中不会发生,因为父级仍然有一个用于该管道写入端的打开文件描述符。

    同时,父级在第二个
  10. wait()
  11. 处被阻止。与之前的

    wait()

     一样,如果 
    grep
     产生足够大的输出来填充其输出所连接的管道的缓冲区,那么这本身就可能是一个问题。然而,在这种情况下,这是没有意义的,因为父进程推迟关闭其 
    pipe1
     文件描述符的副本,直到 
    wait()
     返回之后,而同时,
    grep
     进程无法正常终止,直到父进程关闭写操作为止。该管子的末端。一旦 
    grep
     消耗了 
    ls
     的所有输出,两个进程都无法取得进展——它们陷入僵局。
    
    

    您可以通过让父级在分叉第二个子级时至少关闭 pipeline1 的写入端来解决这个特定的死锁,此时父级无论如何都不再使用该管道。尽快关闭不需要的管端是良好的编程习惯,几乎是必不可少的。但是无论如何,消除
  12. wait(0)
调用是必要的,以避免我提到的其他问题,并且这样做足以让父级尽快执行所有必要的管道端关闭,以解决死锁。

© www.soinside.com 2019 - 2024. All rights reserved.