【问题标题】:Can not understand the pipe() in my own shell无法理解我自己的 shell 中的 pipe()
【发布时间】:2013-05-08 06:49:34
【问题描述】:

这是我为自己的 shell 找到的代码。它工作正常,但我无法理解的是代码的管道部分。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

char* cmndtkn[256];
char buffer[256];
char* path=NULL;
char pwd[128];


int main(){

//setting path variable 
    char *env;
    env=getenv("PATH"); 
    putenv(env);
    system("clear");


printf("\t MY OWN SHELL !!!!!!!!!!\n ");
printf("_______________________________________\n\n");

while(1){

    fflush(stdin);
    getcwd(pwd,128);
    printf("[MOSH~%s]$",pwd);
    fgets(buffer,sizeof(buffer),stdin);
    buffer[sizeof(buffer)-1] = '\0';

    //tokenize the input command line   
    char* tkn = strtok(buffer," \t\n");
    int i=0;
    int indictr=0;



        // loop for every part of the command
        while(tkn!=NULL)
        {

            if(strcoll(tkn,"exit")==0 ){
                exit(0);                
            }

            else if(strcoll(buffer,"cd")==0){
            path = buffer;
            chdir(path+=3);
            }

            else if(strcoll(tkn,"|")==0){
            indictr=i;
            }

            cmndtkn[i++] = tkn;
            tkn = strtok(NULL," \t\n");
        }cmndtkn[i]='\0';

// execute when command has pipe. when | command is found indictr is greater than 0.
    if(indictr>0){

        char* leftcmnd[indictr+1];
        char* rightcmnd[i-indictr];
        int a,b;

        for(b=0;b<indictr;b++)
            leftcmnd[b]=cmndtkn[b];
        leftcmnd[indictr]=NULL;

        for(a=0;a<i-indictr-1;a++)
            rightcmnd[a]=cmndtkn[a+indictr+1];
        rightcmnd[i-indictr]=NULL;

        if(!fork())
        {   
            fflush(stdout);
            int pfds[2];
            pipe(pfds);

                if(!fork()){
                    close(1);
                    dup(pfds[1]);
                    close(pfds[0]);
                    execvp(leftcmnd[0],leftcmnd);
                }   
                else{
                    close(0);
                    dup(pfds[0]);
                    close(pfds[1]);
                    execvp(rightcmnd[0],rightcmnd);
                }
        }else
            wait(NULL);

//command not include pipe 

    }else{
        if(!fork()){
        fflush(stdout);
        execvp(cmndtkn[0],cmndtkn);

        }else
            wait(NULL);
    }

}

}

使用参数0和1调用close()的目的是什么?调用dup()有什么作用?

【问题讨论】:

  • @user315052 @xaxxon 我在理解cd 部分时也遇到了问题。我不明白为什么它将缓冲区分配给chdir(path+=3) 的路径和代码段。有人可以解释一下吗..
  • @VirajLakshitha:你不能像这样继续扩大你的问题范围。如果您必须提出一个新问题,但下次要更加努力地证明您已经尝试自己回答这个问题。否则,这不是一个真正的问题,而是要求有人为你做作业。

标签: c shell unix pipe


【解决方案1】:

在 Unix 上,dup() 调用使用编号最小的未使用文件描述符。因此,在调用dup() 之前的close(1) 是强制dup() 使用文件描述符1。close(0) 也是如此。

所以,别名是让进程使用管道的写端为stdout(文件描述符1用于控制台输出),而管道的读端为stdin(文件描述符0用于控制台输入)。

代码可能用dup2() 来表达更清楚。

dup2(fd[1], 1); /* alias fd[1] to 1 */

从您关于ls | sort 如何工作的问题来看,您的问题不仅限于为什么要进行dup() 系统调用。您的问题实际上是 Unix 中的管道是如何工作的,以及 shell 命令管道是如何工作的。

Unix 中的管道是一对文件描述符,它们在可写描述符上写入数据允许从可读描述符中读取数据。 pipe() 调用将这对以数组形式返回,其中第一个数组元素可读,第二个数组元素可写。

在 Unix 中,fork() 后跟某种 exec() 是生成新进程的唯一方法(还有其他库调用,例如创建进程的 system()popen(),但它们调用fork() 并在引擎盖下执行 exec())。 fork() 产生一个子进程。子进程从调用中看到 0 的返回值,而父进程看到一个非零返回值,它要么是子进程的 PID,要么是表示发生错误的 -1

子进程是父进程的副本。这意味着当子修改变量时,它正在修改驻留在自己进程中的变量的副本。父级看不到修改发生,因为父级拥有原始副本)。但是,可以使用形成管道的重复文件描述符对来允许其父进程的子进程相互通信。

所以,ls | sort 意味着产生了两个进程,ls 写入的输出被sort 读取为输入。两个进程意味着对fork() 的两次调用以创建两个子进程。一个子进程将exec()ls 命令,另一个子进程将exec()sort 命令。它们之间使用管道以允许进程相互通信。 ls 进程写入管道的可写端,sort 进程从管道的可读端读取。

ls 进程在发出close(1) 后被dup() 调用强制写入管道的可写端。在close(0) 之后调用dup() 会强制sort 进程读取管道的可读端。

此外,关闭管道文件描述符的close() 调用用于确保ls 进程是唯一对可写fd 具有开放引用的进程,sort 进程是只有对可读 fd 有开放引用的进程。这一步很重要,因为在ls 退出后,它将关闭 fd 的可写端,sort 进程将期望看到一个 EOF 作为结果。但是,如果其他进程仍然打开可写 fd,则不会发生这种情况。

【讨论】:

  • 感谢您的回答。你能解释一下如果我输入命令 'ls | 会发生什么吗?种类' 。我知道“ls”的输出将成为“排序”的输入。但是怎么执行呢??我问这个是因为“ls”和“排序”命令存储在两个不同的数组中。当它们位于 if 块和相关的 else 块中时,如何使用 'execvp' 执行它们。 @xaxxon
【解决方案2】:

http://en.wikipedia.org/wiki/Standard_streams#Standard_input_.28stdin.29

stdin 是文件描述符 0。

stdout 是文件描述符 1。

在 !fork 部分中,进程关闭 stdout 然后在 pfds[1] 上调用 dup,根据:

http://linux.die.net/man/2/dup

在最低可用位置创建指定文件描述符的副本,该位置将为 1,因为它刚刚关闭(并且标准输入尚未关闭)。这意味着发送到标准输出的所有内容都将真正进入 pfds[1]。

所以,基本上,它设置了两个新的进程来相互交谈。 !fork 部分用于将数据发送到标准输出(文件描述符 1)的新子节点,父节点(else 块)关闭标准输入,因此当它尝试从标准输出读取时,它实际上是从 pfds[0] 读取的。

每个进程都必须关闭它不使用的 pfds 中的文件描述符,因为现在该进程已经分叉了文件有两个打开的句柄。每个进程现在都执行到左/右命令,但新的标准输入和标准输出映射仍然用于新进程。

这里解释了两次分叉:Why fork() twice

【讨论】:

  • 感谢您的回答。你能解释一下如果我输入命令 ls | sort 会发生什么。我知道ls 的输出将成为sort 的输入。但是怎么执行呢??我问这个是因为lssort 命令存储在两个不同的数组中。当它们在 if 块和相关的 else 块中时,如何使用 execvp 执行它们。
  • 'ls' 将结果写入标准输出,对吗? “排序”从标准输入读取。现在它们已连接,因为“ls”实际上正在写入管道的一端,“排序”正在读取它的标准输入。
  • 你能解释一下为什么在执行管道时我应该使用两个fork()系统调用吗??
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-08-06
  • 2015-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多