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

In C, why is my program stuck on wait() when trying to pipe multiple commands?

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

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

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

问题是程序在第二个等待(NULL)时卡住了。在我的代码中,这是'printf("
");'。程序不会终止,并且一直停留到我发送中断为止。

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

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

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

int main(){

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
//### 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\
"
);
        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",但程序不会终止。


您没有在父进程中关闭管道的写端,因此它们都没有真正完全关闭,因此所有子级都没有获得EOF,因此所有子级都没有(除了echo不会读取输入)退出。


您说的是:

... while the parent waits for execvp to finish

wait(2)从不等待execvp(2)系统调用完成,而是等待由execvp(2)启动的整个程序完成。

BTW,execvp(2)在没有错误的情况下不会返回。一件好事是在stderr中显示失败的原因,而不是制作exit(1),因为退出代码是由用户检查的,并且您不知道发生了什么。

之类的东西

1
2
3
4
execvp(...);
fprintf(stderr,"EXECVP: %s: %s\
"
, cmds?[0], strerror(errno));
exit(1);

将非常有效。它只是在当前进程中启动另一个程序,即您从父级fork()所创建的子级。

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

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

最好是等到它们全部结束时...正如您所说,您已经成功完成了三个fork(),因此您必须执行三个wait()才能正确结束。在这种情况下

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

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