【问题标题】:Implement a pipe in C: Is it necessary to fork?在 C 中实现管道:是否需要分叉?
【发布时间】:2018-09-23 20:50:30
【问题描述】:

我正在尝试在 C 中实现一个 Linux 管道链。例如:

grep file | ls | wc

因此,有一个代码将参数拆分为以管道为分隔符的标记,并将每个部分发送到以下函数,并使用整数指定它是否位于管道之前:

int control_flow(char** args, int precedes){

    int stdin_copy = dup(0);
    int stdout_copy = dup(1);

    // if the command and its args precedes a pipe
    if (precedes){

        int fd[2];

        if (pipe(fd) == -1){
            fprintf(stderr, "pipe failed\n");
        }

        if (dup2(fd[1], 1)!=1)
            perror("dup2 error 1 to p_in\n"); // 1 points to pipe's input

        status = turtle_execute(args); // executes the argument list, output should go into the pipe

       // Code stops running here

        if (dup2(fd[0], 0)!=0)
            perror("dup2 error 0 to p_out\n"); // 0 points to pipe's output, any process that reads next will read from the pipe

        if (dup2(stdout_copy, 1)!=1)
            perror("dup2 error 1 to stdout_copy\n"); // 1 points back to stdout

    }

    // if the command does not precede a pipe
    else{

        status = turtle_execute(args); // input to this is coming from pipe

        if (dup2(stdin_copy, 0)!=0)  // 0 points back to stdin
            perror("dup2 error 1 to stdin_copy");

    }

    return 0;
}

我的代码在第一个命令执行后停止运行。我怀疑在使用这个管道之前有必要分叉一个进程,这是为什么呢?如果是这样,我如何在我的代码中做到这一点而不改变我打算做什么?

编辑: 这大致就是turtle_execute 所做的:

turtle_execute(args){
    if (args[0] is cd or ls or pwd or echo)
         // Implement by calling necessary syscalls
    else
         // Do fork and exec the process

所以无论我在哪里使用exec,我都首先使用fork,因此替换进程应该不是问题。

【问题讨论】:

  • 什么是turtle_execute
  • @melpomene 它是一个执行参数的函数。如果它是内置的,则编写了一个实现,否则我分叉并执行所需的命令。
  • grep file | ls | wc 不是一个明智的管道(假设ls 的实现足够标准); ls 命令不读取标准输入,因此grep 部分不会做任何有用的事情。
  • 这里没有足够的代码让任何人都能够解释你如何使用你展示的小代码。您需要创建一个 MCVE (minimal reproducible example)。目前尚不清楚为连接到进程而创建的管道的写入端如何连接到写入进程的标准输出,或者管道的读取端如何连接到管道中下一个进程的标准输入。用内置的命令替换像ls 这样的一大段代码似乎很有趣。我冒昧地猜测您的实现并没有涵盖甚至只是 POSIX ls 的所有可能性,更不用说 GNU。
  • 虽然形式上没有必要 fork 来使用管道,但在少数情况下管道到当前进程是有意义的。您也许可以在多线程进程的两个线程之间使用管道,但这充其量是不合常规的。而且您不会很容易地获得 EOF 指示(对于写入线程或读取线程 - 另一个线程必须关闭其管道末端)。等等。所以,当你使用管道时,你通常会使用 fork;没有什么说你必须分叉(更不用说你必须使用 exec)。

标签: c linux shell pipe dup2


【解决方案1】:

exec 系统调用将当前进程替换为您正在执行的程序。因此,您的进程在turtle_execute 之后自然会停止工作,因为它已被新进程替换。

要执行一个新进程,您通常会 fork 以创建当前进程的副本,然后在副本中执行。

当您在 shell 中时,通常您键入的每个命令都会被分叉并执行。尝试在 shell 中键入exec 后跟一个命令,你会发现一旦该命令执行完毕,shell 就会终止,因为在这种情况下它不会分叉。

编辑

我建议您查看 pipe(2) 手册页 (http://man7.org/linux/man-pages/man2/pipe.2.html#EXAMPLE) 上的示例。它显示了使用管道的常用方法:

  • 调用pipe 来创建管道
  • 调用fork来fork进程
  • 取决于是孩子还是父母关闭管道的一端并使用另一端

我认为您的问题可能是您在分叉之前将管道的写入端设置为标准输出,从而导致父级和子级都有一个开放的写入端。这可能会阻止发送 EOF,因为一个写入端仍处于打开状态。

我只能猜测在大多数 turtle_execute 中会发生什么,但是如果你在一个进程上分叉、执行,然后在另一个进程上等待它,而不消耗管道中的数据,它可能会填满管道并到达写入点被阻止。在写入数据时,您应该始终使用管道中的数据。毕竟它是管道而不是水箱。有关更多信息,请查看“管道容量”部分下的 pipe(7) 手册页。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-07-26
    • 2014-03-21
    • 1970-01-01
    • 1970-01-01
    • 2010-12-14
    • 1970-01-01
    相关资源
    最近更新 更多