用C多个管道的实施 [英] Implementation of multiple pipes in C

查看:99
本文介绍了用C多个管道的实施的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想实现我在C.外壳多个管道,我发现这个教程的网站和我做的功能是基于这个例子。这里的功能

 无效executePipes(CMDLINE *命令,字符* userInput){
    INT numPipes = 2 * countPipes(userInput);
    INT状态;
    INT I = 0,J = 0;
    INT pipefds [numPipes]    对于(I = 0; I&≤(numPipes); I + = 2)
        管(pipefds + I);    而(命令!= NULL){
        如果(叉()== 0){            如果(J!= 0){
                dup2(pipefds [J - 2],0);
            }            如果(命令 - >!下次= NULL){
                dup2(pipefds [J + 1],1);
            }            对于(I = 0; I&≤(numPipes);我++){
                关闭(pipefds [I]);
            }
            如果(execvp(*命令 - >参数,命令 - >参数)小于0){
                PERROR(*命令 - >参数);
                出口(EXIT_FAILURE);
            }
        }        其他{
                如果(命令!= NULL)
                    命令=命令 - >接下来,                J + = 2;
                对于(I = 0; I&≤(numPipes);我++){
                   关闭(pipefds [I]);
                }
               而(waitpid函数(0,0,0)℃,);
        }
    }}

例如像 LS执行它并键入命令后| grep的仓,外壳只是挂起那里,不输出任何结果。我确信我关闭了所有的管道。但它只是挂在那儿。我认为这是在 waitpid函数这是问题。我删除了 waitpid函数并执行后,我没有得到任何结果。我做错了什么?谢谢你。

加code:

 无效runPipedCommands(CMDLINE *命令,字符* userInput){
    INT numPipes = countPipes(userInput);    INT状态;
    INT I = 0,J = 0;    将为pid_t PID;    诠释pipefds [2 * numPipes]。    对于(I = 0; I&2 *(numPipes);我++){
        如果(管道(pipefds + I * 2)小于0){
            PERROR(管子);
            出口(EXIT_FAILURE);
        }
    }    同时,(命令){
        PID =叉();
        如果(PID == 0){            //如果不是第一个命令
            如果(J!= 0){
                如果(dup2(pipefds [(J-1)* 2],0)小于0){
                    PERROR(dup2); /// J-2 0 J + 1 1
                    出口(EXIT_FAILURE);
                    //输出(!J = 0 DUP(pipefd [%D] 0])\\ n,J-2);
                }
            //如果不是最后一个命令
            如果(命令 - >下面){
                如果(dup2(pipefds [J * 2 + 1],1) - = 0){
                    PERROR(dup2);
                    出口(EXIT_FAILURE);
                }
            }            对于(I = 0; I&2 * numPipes;我++){
                    关闭(pipefds [I]);
            }            如果(execvp(*命令 - >参数,命令 - >参数)小于0){
                    PERROR(*命令 - >参数);
                    出口(EXIT_FAILURE);
            }
        }否则如果(PID℃,){
            PERROR(错误);
            出口(EXIT_FAILURE);
        }        命令=命令 - >接下来,
        J ++;
    }
        对于(I = 0; I&2 * numPipes;我++){
            关闭(pipefds [I]);
            看跌期权(封闭管道父);
        }        而(waitpid函数(0,0,0)&下; = 0);    }}


解决方案

我相信这里的问题是,你的等待,而且是创造孩子一样循环内关闭。在第一次迭代中,孩子会执行exec(这会破坏孩子的程序,你的第一个命令覆盖它),然后父进程关闭所有的文件描述符,等待为孩子完成之前迭代上创建下一子。在这一点上,因为父母已经关闭了所有的管道,任何进一步的孩子会有什么写入或读取。既然你不检查您的通话dup2的成功,这是怎么回事未注意到的。

如果你想保持相同的循环结构,你需要确保家长只关闭该已使用的文件描述符,但保留那些单独都没有。然后,已创建的所有孩子后,你的父母可以等待。

修改:我是混合起来的父/子在我的答案,但推理仍然成立:即对进入到餐桌的过程中再次关闭所有管道的副本,所以任何处理后第一叉将没有有效的文件描述符读取从/写。

伪code,使用管道创建一个数组前期:

  / *父在一开始*创建所有需要的管材/
对于(i = 0; I< NUM不锈钢管;我++){
    如果(管道(pipefds + I * 2)小于0){
        PERROR和退出
    }
}commandc = 0
同时,(命令){
    PID =叉()
    如果(PID == 0){
        / *孩子得到输入从previous命令,
            如果它不是第一个命令* /
        如果(不是第一个命令){
            如果(dup2(pipefds [(commandc-1)* 2],0)≤){
                PERROR和退出
            }
        }
        / *子输出到下一个命令,如果它不是
            最后一个命令* /
        如果(不是最后一个命令){
            如果(dup2(pipefds [commandc * 2 + 1],1) - = 0){
                PERROR和退出
            }
        }
        关闭所有管道FDS
        execvp
        PERROR和退出
    }否则如果(PID℃,){
        PERROR和退出
    }
    CMD = CMD->接着
    commandc ++
}/ *父进程关闭其所有副本的结尾* /
对于(I = 0; I&2 * NUM-管道;我++){
    关闭(pipefds [I]);
}

在此code,原来的父进程会为每个命令一个孩子,因此生存的整个考验。孩子们检查,看看他们是否应该从previous命令来获得他们的意见,如果他们要他们的输出发送到下一个命令。然后,他们关闭所有的管道文件描述符,然后EXEC副本。直到它的创建针对每个命令的孩子家长没有做任何事情,但叉。然后,它会关闭所有的描述符的副本,并可以继续等待。

创建所有必须先对管道,然后在循环管理它们,是棘手的并且需要一些阵列算术。我们的目标,不过,看起来是这样的:

  CMD0 CMD1 CMD2 CMD3 CMD4
   PIPE0 PIPE1 pipe2 PIPE3
   [0,1] [2,3] [4,5] [6,7]

认识到,在任何时候,你只需要两套管道(管道到previous命令和管道的下一个命令),可以简化您的code,使其多了几分稳健。 Ephemient给出了这种伪code <一个href=\"http://stackoverflow.com/questions/916900/having-trouble-with-fork-pipe-dup2-and-exec-in-c/\">here.他的code是清洁的,因为父母和孩子不必做无谓的循环,关闭非必要的文件描述符因为家长可以叉后立即轻松关闭文件描述符的副本。

作为一个方面说明:您应经常检查管道,dup2,叉,和exec的返回值

编辑2 :错字伪code。 OP:NUM-管道将管道的数目。例如,LS | grep的富|排序-r将有2个管道

I'm trying to implement multiple pipes in my shell in C. I found a tutorial on this website and the function I made is based on this example. Here's the function

void executePipes(cmdLine* command, char* userInput) {
    int numPipes = 2 * countPipes(userInput);
    int status;
    int i = 0, j = 0;
    int pipefds[numPipes];

    for(i = 0; i < (numPipes); i += 2)
        pipe(pipefds + i);

    while(command != NULL) {
        if(fork() == 0){

            if(j != 0){
                dup2(pipefds[j - 2], 0);
            }

            if(command->next != NULL){
                dup2(pipefds[j + 1], 1);
            }    

            for(i = 0; i < (numPipes); i++){
                close(pipefds[i]);
            }
            if( execvp(*command->arguments, command->arguments) < 0 ){
                perror(*command->arguments);
                exit(EXIT_FAILURE);
            }
        }

        else{
                if(command != NULL)
                    command = command->next;

                j += 2;
                for(i = 0; i < (numPipes ); i++){
                   close(pipefds[i]);
                }
               while(waitpid(0,0,0) < 0);
        }
    }

}

After executing it and typing a command like for example ls | grep bin, the shell just hangs there and doesn't output any result. I made sure I closed all pipes. But it just hangs there. I thought that it was the waitpid that's was the problem. I removed the waitpid and after executing I get no results. What did I do wrong? Thanks.

Added code:

void runPipedCommands(cmdLine* command, char* userInput) {
    int numPipes = countPipes(userInput);

    int status;
    int i = 0, j = 0;

    pid_t pid;

    int pipefds[2*numPipes];

    for(i = 0; i < 2*(numPipes); i++){
        if(pipe(pipefds + i*2) < 0) {
            perror("pipe");
            exit(EXIT_FAILURE);
        }
    }

    while(command) {
        pid = fork();
        if(pid == 0) {

            //if not first command
            if(j != 0){
                if(dup2(pipefds[(j-1) * 2], 0) < 0){
                    perror(" dup2");///j-2 0 j+1 1
                    exit(EXIT_FAILURE);
                    //printf("j != 0  dup(pipefd[%d], 0])\n", j-2);
                }
            //if not last command
            if(command->next){
                if(dup2(pipefds[j * 2 + 1], 1) < 0){
                    perror("dup2");
                    exit(EXIT_FAILURE);
                }
            }

            for(i = 0; i < 2*numPipes; i++){
                    close(pipefds[i]);
            }

            if( execvp(*command->arguments, command->arguments) < 0 ){
                    perror(*command->arguments);
                    exit(EXIT_FAILURE);
            }
        } else if(pid < 0){
            perror("error");
            exit(EXIT_FAILURE);
        }

        command = command->next;
        j++;
    }
        for(i = 0; i < 2 * numPipes; i++){
            close(pipefds[i]);
            puts("closed pipe in parent");
        }

        while(waitpid(0,0,0) <= 0);

    }

}

解决方案

I believe the issue here is that your waiting and closing inside the same loop that's creating children. On the first iteration, the child will exec (which will destroy the child program, overwriting it with your first command) and then the parent closes all of its file descriptors and waits for the child to finish before it iterates on to creating the next child. At that point, since the parent has closed all of its pipes, any further children will have nothing to write to or read from. Since you are not checking for the success of your dup2 calls, this is going un-noticed.

If you want to keep the same loop structure, you'll need to make sure the parent only closes the file descriptors that have already been used, but leaves those that haven't alone. Then, after all children have been created, your parent can wait.

EDIT: I mixed up the parent/child in my answer, but the reasoning still holds: the process that goes on to fork again closes all of its copies of the pipes, so any process after the first fork will not have valid file descriptors to read to/write from.

pseudo code, using an array of pipes created up-front:

/* parent creates all needed pipes at the start */
for( i = 0; i < num-pipes; i++ ){
    if( pipe(pipefds + i*2) < 0 ){
        perror and exit
    }
}

commandc = 0
while( command ){
    pid = fork()
    if( pid == 0 ){
        /* child gets input from the previous command,
            if it's not the first command */
        if( not first command ){
            if( dup2(pipefds[(commandc-1)*2], 0) < ){
                perror and exit
            }
        }
        /* child outputs to next command, if it's not
            the last command */
        if( not last command ){
            if( dup2(pipefds[commandc*2+1], 1) < 0 ){
                perror and exit
            }
        }
        close all pipe-fds
        execvp
        perror and exit
    } else if( pid < 0 ){
        perror and exit
    }
    cmd = cmd->next
    commandc++
}

/* parent closes all of its copies at the end */
for( i = 0; i < 2 * num-pipes; i++ ){
    close( pipefds[i] );
}

In this code, the original parent process creates a child for each command and therefore survives the entire ordeal. The children check to see if they should get their input from the previous command and if they should send their output to the next command. Then they close all of their copies of the pipe file descriptors and then exec. The parent doesn't do anything but fork until it's created a child for each command. It then closes all of its copies of the descriptors and can go on to wait.

Creating all of the pipes you need first, and then managing them in the loop, is tricky and requires some array arithmetic. The goal, though, looks like this:

cmd0    cmd1   cmd2   cmd3   cmd4
   pipe0   pipe1  pipe2  pipe3
   [0,1]   [2,3]  [4,5]  [6,7]

Realizing that, at any given time, you only need two sets of pipes (the pipe to the previous command and the pipe to the next command) will simplify your code and make it a little more robust. Ephemient gives pseudo-code for this here. His code is cleaner, because the parent and child do not have to do unnecessary looping to close un-needed file descriptors and because the parent can easily close its copies of the file descriptors immediately after the fork.

As a side note: you should always check the return values of pipe, dup2, fork, and exec.

EDIT 2: typo in pseudo code. OP: num-pipes would be the number of pipes. E.g., "ls | grep foo | sort -r" would have 2 pipes.

这篇关于用C多个管道的实施的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆