为什么我的带有两个管道的C程序会挂起?

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

我正在尝试编写一个 C 程序来执行类似管道的操作

ls | wc | wc
。我已经完成了
ls | wc
,而且效果很好,但我不明白为什么我的程序停止在指定行的子进程上。

int main (void)
{
    pid_t pid_fils, pid_pfils;

    int fd[2], fd2[2];

    if(pipe(fd)==-1 || pipe(fd2)==-1)
    {
        printf("pipe failed!");
        return 1;
    }

    printf("program started\n");
    pid_fils=fork();
    if(pid_fils==0)
    {
        pid_pfils=fork();
        if(pid_pfils==0)
        {
            //action3
            printf("I am the grandson\n");
            close(fd[0]);//close read side
            dup2(fd[1],1);//connect write with stdout
            close(fd[1]);//close write side
            execlp("ls","ls",(char*)0);
            //execvp("ls",argv3);
            return 0;/*actions grandson*/
        }
        else
        {
            //action2
            printf("I am the son\n");
            wait();
            printf("son, wait ok\n");
            >close(fd[1]);  //close read side
            >dup2(fd[0],0); //connect write with stdin
            >close(fd[0]);  //close read side
            
            ///////pipe2////
           > close(fd2[0]);  //close read side
            >dup2(fd2[1],1); //connect write with stdout/*it stops here -can't display "ok!"*/
            printf("ok!\n");    
            >close(fd2[1]);  //close write side

            execlp("wc","wc",(char*)0);
            printf("error exec returned!\n");    
            return 0;
        }
    }
    else
    {
        ///action1
        printf("I am the parent\n");
        wait();
        printf("parent,wait ok\n");
        close(fd2[1]);  //close write side, 
        dup2(fd2[0],0); //connect read with stdin
        close(fd2[0]);  //close read side
        execlp("wc","wc",(char*)0);
        return 0;/*the parent*/
    }
    return 1;
}
c linux ubuntu pipe systems-programming
3个回答
4
投票

确保关闭所有未使用的描述符。对于您的情况,最简单的解决方案是将 pipeline(fd) 的创建移至第一个 if 块(在第一个子流程中)。问题是,只要任何进程可能写入管道,读取器就不会收到 EOF,因此不会终止。

if(pipe(fd2)==-1)
{
    printf("pipe failed!");
    return 1;
}

printf("program started\n");
pid_fils=fork();
if(pid_fils==0)
{
    if(pipe(fd)==-1)
    {
        printf("pipe failed!");
        return 1;
    }
    pid_pfils=fork();

我还应该提到,您可能需要重新考虑等待调用。不确定您打算用它们做什么,但您不希望“ls”进程阻止输出,因为阅读器尚未启动。


1
投票
dup2(fd2[1],1);

上面的行将首先关闭描述符 1 处的文件,然后将描述符从 fd2[1] 复制到 1。

1 是标准输出。这意味着该调用关闭了标准输出。

printf 打印到 stdout,这意味着 printf 打印到 1,现在已分配给管道 fd2

所以你的ok进入了管道而不是在屏幕上。

尝试

        //action2
        printf("I am the son\n");
        wait();
        printf("son, wait ok\n");
        close(fd[1]);  //close read side
        dup2(fd[0],0); //connect write with stdin
        close(fd[0]);  //close read side

        ///////pipe2////
        int my_terminal_out = dup(1);
        close(fd2[0]);  //close read side
        dup2(fd2[1],1); //connect write with stdout/*it stops here -can't display "ok!"*/
        fprintf(my_terminal_out, "ok!\n");    
        close(fd2[1]);  //close write side

未经测试。此外,您还应该测试其余代码是否存在类似的错误。

+DrC 所说的。


0
投票

扩展这个SO答案,我们可以创建一个额外的管道,分叉另一个进程,并相应地连接管道。

// two-pipes.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

// C implementation for the `ls -la | wc | wc` pipeline.
int main(int argc, char *argv[]) {
    // We declare two pipes, one for each pair of processes, i.e.,
    // ls -al pipefd1 wc pipefd2 wc
    int pipefd1[2];
    int pipefd2[2];
    pid_t ls_pid, wc_pid1, wc_pid2;

    pipe(pipefd1);
    pipe(pipefd2);

    // CHILD PROCESS: ls
    if ((ls_pid = fork()) == 0) {
        // We close-and-then-connect STDOUT to pipefd1's write end so the
        // process write to the pipe instead of the screen.
        dup2(pipefd1[1], STDOUT_FILENO);

        // Since STDOUT_FILENO also refers to pipefd's write end, we can
        // close this file descriptor; it's no longer needed.
        close(pipefd1[1]);

        // The process doesn't use these file descriptors so we close them.
        close(pipefd1[0]);
        close(pipefd2[0]);
        close(pipefd2[1]);

        // Execute the `ls` command, and exit if errors out.
        if ((execl("/bin/ls", "ls", "-al", (char *) NULL)) < 0) exit(0);
    }
    else if (ls_pid < 0) {
        fprintf(stderr, "failed to fork ls process");
        exit(0);
    }

    // CHILD PROCESS: wc (1)
    if ((wc_pid1 = fork()) == 0) {
        // We close-and-then-connect STDIN and STDOUT to pipefd1's read and
        // and pipefd2's write end so the process read from pipefd1 and write
        // to pipefd2 instead of from the keyboard and to the screen respectively.
        dup2(pipefd1[0], STDIN_FILENO);
        dup2(pipefd2[1], STDOUT_FILENO);

        // Since STDIN_FILENO and STDOUT_FILENO also refer to pipefd1's read
        // end and pipefd2's write end respectively, we can close these file
        // descriptors.
        close(pipefd1[0]);
        close(pipefd2[1]);

        // The process doesn't use these file descriptors so we close them.
        close(pipefd1[1]);
        close(pipefd2[0]);

        // Execute the `wc` command, and exit if errors out.
        if ((execl("/usr/bin/wc", "wc", (char *) NULL)) < 0) exit(0);
    }
    else if (wc_pid1 < 0) {
        fprintf(stderr, "failed to fork wc process");
        exit(0);
    }

    // CHILD PROCESS: wc (2)
    if ((wc_pid2 = fork()) == 0) {
        // We close-and-then-connect STDIN to pipefd2's read end so the process
        // read from the pipe instead of from the keyboard.
        dup2(pipefd2[0], STDIN_FILENO);

        // Since STDIN_FILENO also refers to the pipefd2's read end, we can
        // close this file descriptor; it's no longer needed.
        close(pipefd2[0]);

        // The process doesn't use these file descriptors so we close them.
        close(pipefd1[0]);
        close(pipefd1[1]);
        close(pipefd2[1]);

        // Execute the `wc` command, and exit if errors out.
        if ((execl("/usr/bin/wc", "wc", (char *) NULL)) < 0) exit(0);
    }
    else if (wc_pid1 < 0) {
        fprintf(stderr, "failed to fork wc process");
        exit(0);
    }
    
    // PARENT PROCESS

    // The parent process isn't using the pipes, however these descriptors are
    // another references to the pipe's read and write ends and we must close
    // them. Otherwise, it doesn't send the EOF so the children can continue
    // (children block until all input has been processed).
    close(pipefd1[0]);
    close(pipefd1[1]);
    close(pipefd2[0]);
    close(pipefd2[1]);

    // The parent process waits for the three child processes to finish before
    // exiting.
    int ls_status, wc_status1, wc_status2;
    pid_t ls_wpid = waitpid(ls_pid, &ls_status, 0);
    pid_t wc_wpid1 = waitpid(wc_pid1, &wc_status1, 0);
    pid_t wc_wpid2 = waitpid(wc_pid2, &wc_status2, 0);

    // Return main's status based on whether the parent process waited both
    // child processes successfully. Status based only on `ls_status`.
    return
        ls_pid == ls_wpid && WIFEXITED(ls_status) &&
        wc_pid1 == wc_wpid1 && WIFEXITED(wc_status1) &&
        wc_pid2 == wc_wpid2 && WIFEXITED(wc_status2)
        ? WEXITSTATUS(ls_status)
        : -1;
}

编译并运行:

$ ls -la
total 112
drwxr-xr-x   8     256 Nov 20 13:52 .
drwxr-x---+ 91    2912 Nov 20 13:55 ..
-rwxr-xr-x   1   33896 Nov 20 13:48 a.out
-rw-r--r--   1      45 Nov 17 15:33 error.log
-rw-r--r--@  1    2390 Nov 20 09:51 ls-wc-pipes.c
-rw-r--r--   1     116 Nov 17 16:01 out.log
-rw-r--r--@  1    2647 Nov 20 09:49 so-pipes.c
-rw-r--r--@  1    4021 Nov 20 13:52 two-pipes.c
$ gcc two-pipes.c && ./a.out
       1       3      25
© www.soinside.com 2019 - 2024. All rights reserved.