管道在c linux中连接3个进程来执行命令

时间:2017-02-02 18:37:45

标签: c linux pipe fork

我的任务是编写一个执行命令的C程序" ls -l / bin / ?? | grep rwxr-xr-x |排序&#34 ;.有3个子进程,每个进程分别执行一个命令,并通过管道将结果发送到下一个子进程。我使用瑞典语修改后的debian验证,因此错误信息是瑞典语,但我会翻译我得到的错误,它的内容如下:sort:failed to status - :未知的fileidentifier。

也许我的管道无法正常工作,我对close()命令不太确定。我很确定错误来自管道。如果有人可以运行该程序并收到英文错误消息,将不胜感激。

#include <stdio.h> 
#include <sys/types.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <fcntl.h> 
#include <assert.h> 
#include <errno.h> 
#include <string.h> 


int main() 
{ 
    int ret;  
    int fds1[2], fds2[2], fds3[2]; 
    char buf[20]; 

    pid_t pid; 

    ///initiating pipes 
    ret=pipe(fds1); 

    if(ret == -1){ 
        perror("could not pipe"); 
        exit(1); 
    } 

    ret=pipe(fds2); 

    if( ret == -1){ 
        perror("could not pipe"); 
        exit(1); 
    } 

    ret=pipe(fds3); 

    if (ret == -1){ 
        perror("could not pipe"); 
        exit(1); 
    } 

    pid=fork(); 

    if(pid==-1){ 

        fprintf(stderr,"fork failed"); 
        exit(0); 
    } 

    if(pid==0){ 
        ///CHILD 1 

        close(1); 
        dup(fds1[1]); 

        close(fds1[0]); 
        close(fds1[1]); 

        close(0); 
        execlp("/bin/sh","bin/sh", "ls-l /bin/??", (char *)NULL); 
    } 
    else{ 
        wait(0); 
    } 

    pid=fork(); 

    if(pid==-1){ 

        fprintf(stderr,"fork failed"); 
        exit(0); 
    } 

    if(pid==0){ 

        close(0); 
        dup(fds1[0]); 

        close(fds1[0]); 
        close(fds1[1]); 

        close(1); 
        dup(fds2[1]); 

        close(fds2[0]); 
        close(fds2[1]); 

        execlp("/usr/share/grep/", "grep", "rwxr-xr-x", NULL); 
    } 
    else{ 
        wait(0); 
    } 

    close(fds1[0]); 
    close(fds1[1]); 

    pid=fork(); 

    if(pid==-1){ 

        fprintf(stderr,"fork failed"); 
        exit(0); 
    } 

    if(pid==0){ 

        close(0); 
        dup(fds2[0]); 
        close(fds2[0]); 
        close(fds2[1]); 

        execlp("sort", "sort", NULL);
    } 
    else{ 
        wait(0); 
    } 

    close(fds2[0]); 
    close(fds2[1]); 
}  

1 个答案:

答案 0 :(得分:1)

您的代码有几个问题,但在我讨论之前,让我向您介绍一种我最喜欢的预处理器宏:

#define DO_OR_DIE(x, s) do { \
    if ((x) < 0) { \
        perror(s); \
        exit(1);   \
    } \
} while (0)

在适用的宏中使用此宏可以通过替换所有样板错误检查来阐明您的代码。例如,这个:

ret=pipe(fds1); 

if(ret == -1){ 
    perror("could not pipe"); 
    exit(1); 
}

变得只是

DO_OR_DIE(pipe(fds1), "pipe");

这使得查看和关注代码的关键部分变得更加容易,并且也更容易键入。因此,它还减少了跳过错误检查的诱惑,例如您拨打dup()的错误检查。

现在,关于你的代码。对我来说,它不仅表现出你在问题中描述的一个不正当行为,而且展示了三个:

  • 它会发出错误消息“bin / sh:ls -l / bin / ??:没有这样的文件或目录”。
  • 它会发出您描述的错误消息,“sort:stat failed: - :Bad file descriptor”。
  • 它不会终止。

第一条错误消息与您第一次execlp()调用的参数中的多个问题有关。如果要启动shell并指定要运行的命令,而不是从读取命令的文件,则必须将-c选项传递给它。此外,您在ls及其参数之间省略了强制性空格。看起来你想要这个:

execlp("/bin/sh","sh", "-c", "ls -l /bin/??", (char *)NULL); 

暂时搁置第二个问题,让我们转向未能终止。您在这个领域遇到了几个问题,分为以下几类:

  • 保持管道末端打开,确保它们关闭
  • 在错误的位置呼叫wait()

当您在两个进程之间设置管道时,通常需要确保管道两端没有打开的文件描述符,而不是一个进程持有的写入端上的文件描述符,而读取端有一个由其他进程持有。每一端应该只在一个过程中打开一次。由于连接的进程总是从父进程继承这些文件描述符,因此父进程必须关闭其副本(除非父进程本身是通信进程之一,否则父进程必须保持打开状态)。

在写入端的所有打开文件描述符关闭之前,管道读取端的进程将不会在该管道上看到EOF。如果管道的写入端未完全关闭,则运行诸如grepsort之类的读取其输入的程序的子进程将无限期挂起。

当阅读管道的孩子也有一个管道写入端的副本,未使用,或者其中一个兄弟姐妹时,这可能是一个特别不正常的问题。

此外,管道的重点是所涉及的进程同时运行。如果你在开始下一个之后开始wait(),那么至少你会阻止这种并发。然而,更糟糕的是,这也可能导致程序挂起,因为管道具有有限的缓冲容量。如果孩子正在将输出写入管道,但没有人正在读取它,那么管道的缓冲区可以填充容量,此时子块会阻塞。如果父母在启动将耗尽管道的进程之前等待孩子完成,那么你就会陷入僵局。因此,您应首先启动管道中的所有进程,然后等待它们。

修复了代码中的这些问题后,我发现该程序为我发出了不同的错误:

  

execlp:没有这样的文件或目录

(此消息的细节源于我的修复的性质。)这应该特别令人担忧,因为如果execlp()失败,那么它将在调用它的过程中返回。在您的情况下,控制将直接从您的if语句中转移到仅供父项执行的代码中。因此,处理execlp()的错误至关重要。至少应在之后立即添加对exit()_Exit()的通话。

但是什么失败了?嗯,这次是grep。请注意,您指定要以"/usr/share/grep/"执行的命令 - 尾随/是错误的,并且路径本身是可疑的。在我的系统上,正确的路径是/usr/bin/grep,但由于我们正在使用execlp来解析路径中的可执行文件,我们不妨完全省略该路径:

execlp("grep", "grep", "rwxr-xr-x", (char *) NULL);

Etvoilà!在进行了更正后,您的程序也会为我运行。

其他建议:当您关心要复制的文件描述符编号时,请不要使用dup(),例如当您尝试复制到其中一个标准流时。请使用dup2(),这样做的另一个好处是您不需要先关闭指定的文件描述符。