在C语言中,为什么尝试通过管道传递多个命令时我的程序停留在wait()上?

时间:2019-06-13 03:19:23

标签: c pipe fork

我正在编写一个简单的程序来在三个命令之间进行传递。对于每个命令,我派生一个孩子并在其中调用execvp,而父母等待execvp完成(注意,因为我分叉了3次,所以我调用了wait(NULL)3次)

我创建了两个管道pipe_A和pipe_Z。流程类似于:

命令1将其输出写入pipe_A,   命令2从pipe_A读取输入,然后将输出写入pipe_Z,然后   命令3从pipe_Z读取输入,然后写入标准输出。

问题是程序在第二个等待(NULL)时卡住了。在我的代码中,这是'printf(“ reached \ n”);'之后的行。该程序不会终止,并且会阻塞,直到我发送中断为止。

如果我用相同的命令“ echo hello”替换所有三个命令,它不会执行很多操作,但程序至少会结束。

我只用两个命令(“ echo hello”和“ wc”)编写了一个较小的程序(相同的结构),它也卡在了第一次等待(NULL)上。但是,我注意到,如果我在主程序中的第二个命令上调用execvp,则输出将按预期进行打印。但是,该程序也会立即终止(由于execvp),所以这不是我想要的方式。

我怀疑由于缺乏对管道如何工作或wait(NULL)函数调用如何工作的了解而犯了一个非常明显的错误。

int main(){

//### COMMANDS AS STRING ARRAYS ###

    char * cmds1[3] = {"echo","hello", NULL};
    char * cmds2[3] = {"cat","-",NULL};
    char * cmds3[3] = {"wc", NULL};

//### CREATING PIPES ###

    int pipe_A[2];
    int pipe_Z[2];

    pipe(pipe_A);
    pipe(pipe_Z);

//### FIRST FORK FOR FIRST EXECVP ###

    int pid = fork();

    //Child process
    if(pid==0){

        close(pipe_A[0]);
        dup2(pipe_A[1], STDOUT_FILENO);

        execvp(cmds1[0],cmds1);
        exit(1);

    }

    //Parent process
    else if(pid >0){
        wait(NULL);
    }

//### SECOND FORK FOR SECOND EXECVP ###

    int pid2 = fork();

    //Child process
    if(pid2==0){

        close(pipe_A[1]);
        dup2(pipe_A[0],STDIN_FILENO);

        close(pipe_Z[0]);
        dup2(pipe_Z[1], STDOUT_FILENO); 

        execvp(cmds2[0],cmds2);
        exit(1);

    }

    //Parent process
    else if(pid2>0){

        printf("reached\n");
        wait(NULL);

    }

//### THIRD FORK FOR THIRD EXECVP ###

    int pid3 = fork();

    //Child process
    if(pid3==0){

        close(pipe_Z[1]);
        dup2(pipe_Z[0],STDIN_FILENO);

        execvp(cmds3[0],cmds3);
        exit(1);

    }

    //Parent process
    else if(pid2 >0){

        wait(NULL);

    }


//### END OF PROGRAM ###

    return 0;

}

预期输出为“ 1 1 6”,但程序不会终止。

2 个答案:

答案 0 :(得分:3)

您没有在父进程中关闭管道的写端,因此它们中的任何一个都没有真正关闭,因此所有子级都没有获得EOF,因此所有子级都没有(除了{{1} }(不读取输入内容)退出。

答案 1 :(得分:0)

您说:

  

...而父级等待execvp完成

execvp(2) 从不等待,以完成execvp(2)系统调用,但要等待execvp(2)开始的整个程序结束。

顺便说一句,stderr在没有错误的情况下不会返回。一件好事是在exit(1)中显示失败的原因,而不是制作execvp(...); fprintf(stderr, "EXECVP: %s: %s\n", cmds?[0], strerror(errno)); exit(1); ,因为退出代码是由用户检查的,而且您不知道发生了什么。像

fork()

将非常有效。它只是在当前进程中启动另一个程序,即您从父close(2)所生的孩子。

  • 您的第一个问题是,您未使用的文件描述符在父级中保持打开状态,因此管道的读取器进程将被阻塞,等待更多输入,直到管道的写入端全部关闭为止。可以进行写作的流程(其中包括父项)。您在父进程中创建管道,因此,它们也将在子进程中获得。在子级中,您wait(2)的未使用端,但在父级中不这样做(并且必须在等待之前执行),因此正在读取的子级在编写器时将收到文件结尾端关闭了写端,因为这样,不再有任何写进程(就像现在是父进程一样)

  • 第二,这是一个简单的情况,其中三个进程将强制依次结束,但是即使在这种情况下,您也要等待它们完成才能启动下一个进程。由于流程是相互关联的,因此只有在所有流程都有机会交换信息之后。在这种情况下,您需要等待一个完成,然后再启动第二个,而第二个将不会读任何东西,除非它还活着。在您的情况下,第一个close(2)正在等待第一个孩子退出,但是第一个孩子不能fork()(在关闭时被阻止),因为没有人打开管道进行读取(嗯,父级确实这样做了,但预期读取该信息的过程是第二个孩子)。如果您等待第一个进程终止,那么两个进程将永远不会同时存活,因为您要等到第一个进程结束之后再生成第二个进程。

最好是等到最后一遍……就像你说的那样,你已经取得了三个成功 wait(),所以你必须做三个{{1} }才能正确结束。在这种情况下

for (i = 0; i < 3; i++) wait(NULL);

是正确的(但在末尾)。 (即使任何前叉未成功,等待也会检测到该错误并失败并显示错误,因此您不必检查错误)