【问题标题】:c Pipes causing program to hang in some casesc 在某些情况下导致程序挂起的管道
【发布时间】:2016-05-14 06:16:14
【问题描述】:

我正在尝试使用管道将执行的 1 个命令的标准输出链接到另一个命令的标准输入。例如模仿(cmd1 | cmd2)

以下是我的代码的精简版。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int main (int argC, char *argv[])
{

    //Run first command
    int fds[2];
    if (pipe (fds) < 0) {       //Create pipe
        fprintf (stderr, "Pipe Failed\n");
    }

    int pid;
    if ((pid = fork ()) == -1) {
        fprintf (stderr, "Fork 1 Failed\n");
        exit (1);
    }

    if (pid == 0) {             //First child proccess
        close (fds[0]);         //Close input end of pipe
        dup2 (fds[1], STDOUT_FILENO);   //Set stdout to output pipe
        close (fds[1]);         //Close output end of pipe

        fprintf (stderr, "Exec 1 executing now\n");
        execlp ("./addone", "./addone", NULL);  //execute first command - Doesnt cause hang
        //execlp("ls", "ls", NULL);//Causes hang
        fprintf (stderr, "Exec 1 failed\n");
    } else {                    //First parent segment
        int returnStatus;
        waitpid (pid, &returnStatus, 0);        //Wait for child 1 to finish

        fprintf (stderr, "Back to parent 1\n");
    }

    //Run second command
    if ((pid = fork ()) == -1) {
        fprintf (stderr, "Fork 2 failed\n");
    }

    if (pid == 0) {             //second child proccess
        dup2 (fds[0], STDIN_FILENO);    //Set stdin to input pipe
        close (fds[0]);         //Close input end of pipe
        close (fds[1]);         //Close output end of pipe

        fprintf (stderr, "Exec 2 executing now\n");
        execlp ("./addone", "./addone", NULL);  //execute first command - Doesnt cause hang
        //execlp("wc", "wc", NULL);//Causes hang
        fprintf (stderr, "Exec 2 failed\n");
    } else {                    //second parent segment
        int returnStatus;
        waitpid (pid, &returnStatus, 0);        //Wait for child 2 to finish

        fprintf (stderr, "Back to parent 2\n");
        //Done with pipes
        close (fds[0]);
        close (fds[1]);
    }
    return 0;
}

在程序中,我尝试创建一个管道并将第一个 execs 标准输出路由到第二个标准输入。尝试使用 ls | 运行程序时wc 作为我的执行官,我的程序挂在第二个执行官上。但是,当我使用一个简单的程序“./addone”作为我的 exec 时,整个执行都会正确完成。

其中“addone”是一个小程序:

#include<stdio.h>

int main(){
    int value = 0;
    scanf("%d", &value);
    value++;
    printf("%d\n", value);
}

有人建议我的问题可能是有一个输入管道处于打开状态,但我无法确定会发生这种情况的位置,并且它没有解释为什么我的程序在使用我的简单测试程序时运行完美。

任何有关导致此挂起的原因的建议将不胜感激。

【问题讨论】:

  • 您需要在父进程中关闭管道末端。因为pipe 是在fork 之前调用的,所以它在父进程和子进程中都打开。对于您的简单程序来说这不是问题,因为它不会像wc 那样循环等待EOF。如果管道仍被任何进程保持打开状态,EOF 永远不会出现。
  • 我不是已经用 close(fds[0]);关闭(fds[1]);或者我应该将它们移到父级的等待语句之前?
  • 你确定你没有从你的程序中删除一些重要的东西吗?在您发布的示例中,只要正在执行的程序将任何内容写入标准输出,我希望它在第一个 waitpid 处挂起 - 子进程中的 printf 会阻塞,因为没有从管道的另一端读取任何内容,而父母会反过来阻止等待孩子。建议:1)立即关闭父子和子中未使用的管道末端(即在第一次分叉后关闭父中的 fds[1] 等。2)在开始第二个之前不要等待第一个子,最后都等待而是
  • @Killedan9 是的,您必须在waitpitd 之前执行此操作。否则子进程不会退出,他waitpid 永远不会解除阻塞。
  • @davlet “子进程中的 printf 会阻塞,因为管道另一端没有读取任何内容”。我不认为这是正确的。也许您正在考虑fifo。管道没有这种行为。无论如何,正如已经指出的那样,父进程的两端都是开放的。

标签: c unix pipe pipeline


【解决方案1】:

我不知道您的问题是否解决了,但是从 cmets 继续,如果您要构建管道,关键是要了解,如果您在一个进程中写入管道,则必须阅读从下一个流程中的同一管道,如果要继续管道,则写入第二个管道。现在您可以按照链接 C Minishell Adding Pipelines 中所示的方式自动执行该过程,但出于学习目的,如果您只处理单个线性示例,可能更容易理解。

下面是使用两个文件描述符fd1fd2 的代码实现。您的addone 进程将首先从普通的旧stdin 读取,因此不会为读取操作文件描述符。但是,对于它的写入,它必须使用 fd1write-end(因为缺少更好的词)将其输出通过管道传递到下一个子进程。

下一个子进程必须从同一 fd1input-end 读取以接收其输入,并且它必须写入另一个文件的 output-end描述符fd2。两个孩子都退出后,父进程必须从哪里读取?? (最后一个管道是什么……?啊哈!)fd2。因此父级复制fd2read-end(关闭所有其他未使用的端)并读取在初始数字上添加了多少addone 的最终答案。

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

int main (void) {

    int fd1[2], fd2[2];  /* file descriptors 1 & 2 */
    int pid;

    if (pipe (fd1) < 0 || pipe (fd2) < 0) { /* open both pipes */
        fprintf (stderr, "pipe creation failed\n");
    }

    if ((pid = fork ()) == -1) {
        fprintf (stderr, "fork 1 failed\n");
        exit (1);
    }

    if (pid == 0) {                     /* first child */
        dup2 (fd1[1], STDOUT_FILENO);   /* dup write-end of 1st */
        close (fd1[0]);                 /* close all others */
        close (fd2[0]);
        close (fd2[1]);

        fprintf (stderr, "Exec 1 executing now\n");
        execlp ("./addone", "./addone", NULL);
        fprintf (stderr, "Exec 1 failed\n");
    }
    else {
        int returnStatus;
        waitpid (pid, &returnStatus, 0);
        fprintf (stderr, "Back to parent 1\n");
    }

    if ((pid = fork ()) == -1) {
        fprintf (stderr, "Fork 2 failed\n");
    }

    if (pid == 0) {                     /* second child */
        dup2 (fd1[0], STDIN_FILENO);    /* dup read-end of 1st  */
        dup2 (fd2[1], STDOUT_FILENO);   /* dup write-end of 2nd */
        close (fd1[1]);                 /* close all others */
        close (fd2[0]);

        fprintf (stderr, "Exec 2 executing now\n");
        execlp ("./addone", "./addone", NULL);
        fprintf (stderr, "Exec 2 failed\n");
    }
    else {
        int returnStatus;
        waitpid (pid, &returnStatus, 0);
        fprintf (stderr, "Back to parent 2\n");
    }

    dup2 (fd2[0], 0);   /* dup read-end of 2nd */
    close (fd2[1]);     /* close all others */
    close (fd1[0]);
    close (fd1[1]);

    int total;
    scanf ("%d", &total);
    printf ("The total was: %d\n\n", total);

    close (fd2[0]);

    return 0;
}

使用/输出示例

$ echo 10 | ./bin/pipeaddone
Exec 1 executing now
Back to parent 1
Exec 2 executing now
Back to parent 2
The total was: 12

查看一下,如果您有任何问题,请告诉我。一旦你得到它,它就有意义了。如果你有一段时间不使用它,别担心,你会重新学习它:)

【讨论】:

  • 感谢您的详细回复!我设法让我的程序运行起来,我终于明白管道在做什么了。
  • 很高兴我能帮上忙。距离我上次使用这种方法编写一段代码已经过去了大约一年,所以——我再次享受学习它的乐趣:)
猜你喜欢
  • 1970-01-01
  • 2016-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-21
  • 1970-01-01
相关资源
最近更新 更多