【问题标题】:Writing my own shell... stuck on pipes?编写我自己的外壳...卡在管道上?
【发布时间】:2010-11-30 11:39:56
【问题描述】:

在过去的几天里,我一直在尝试编写自己的 shell 实现,但我似乎一直无法让管道正常工作。我能够解析一行并单独分叉管道之间的命令(例如:ls | sort),但似乎无法让它们将输入从一个管道传输到另一个。

我想我只是不明白如何正确使用 dup2() 和管道。

我现在已经包含了我仍然失败的代码... :( 所以卡住了...

void forkAndExecute( char* arrayOfWords[] , vector<pid_t> *vectorOfPIDs , bool hasNextCmd , bool hasPrevCmd) {

int fd[ 2 ];
pid_t pid;

if( hasNextCmd ){
    pipe(fd);
}

pid = fork();

//error if PID < 0
if( pid < 0 ) {
    cerr << ">>> fork failed >>>" << endl;
    exit(-1);
}
//child process if PID == 0
else if( pid == 0 ) {
    if ( hasPrevCmd ){
        dup2(fd[0] , 0);
        close(fd[0]);
        close(fd[1]);

    }
    if ( hasNextCmd ){
        dup2(fd[1],1);
        close(fd[0]);
        close(fd[1]);
    }
    execvp( arrayOfWords[0] , arrayOfWords );
    cout << ">>> command not found >>>" << endl;
    //if logic reaches here, exec failed
    exit(0);
} 
//parent process
else{
    close(fd[0]);
    close(fd[1]);
    //if( ! isLastCmd ){

    //}
    vectorOfPIDs->push_back(pid);
}

}

【问题讨论】:

    标签: c++ unix shell pipe


    【解决方案1】:

    这是一个关于 UNIX 管道的教程,特别是关于如何在类似 shell 的架构中构建管道:

    http://www.cse.ohio-state.edu/~mamrak/CIS762/pipes_lab_notes.html

    没有多少完全编写的代码,但它很好地描述了这些概念。

    您还可以下载几乎任何 shell 的源代码,例如 bashtcshzsh 等。

    【讨论】:

      【解决方案2】:

      尝试阅读source code of Bash,看看他们是如何做到的。

      【讨论】:

      • 恕我直言,阅读 pipe、dup 以及 fork、exec 和 wait 的手册页将不那么令人生畏。这样他就可以通过理解一般机制来学习,而不是研究一个具体而复杂的例子。
      【解决方案3】:

      Here 是我上学期系统编程课上关于管道的笔记。

      【讨论】:

        【解决方案4】:

        几年前当我需要做一个类似的shell时,我使用了Practical Unix Programming这本书。

        它对于许多 IPC 主题的示例非常有用。我的办公桌上仍然有一份我不时参考的副本。使用 2 - 9 美元,物有所值。

        对于它的价值,只是想我会提到它。

        【讨论】:

          【解决方案5】:

          一般流程会为这个基本流程添加错误处理(伪代码):

          pipe(fds)
          if (fork() is child) {
            dup2(fds[1], 1)
            close(fds[0])
            close(fds[1])
            exec("ls")
          }
          if (fork() is child) {
            dup2(fds[0], 0)
            close(fds[0])
            close(fds[1])
            exec("sort")
          }
          close(fds[0])
          close(fds[1])
          wait()
          

          首先创建管道。然后 fork 子进程,以便它们继承它。将文件描述符重新映射为 0 (stdin) 和 1 (stdout),以便进程读取和写入适当的位置。当工作完成时,关闭您不希望子进程看到或阻止的任何剩余文件描述符。执行实际的子进程。等待他们完成,你就完成了!

          【讨论】:

          • 它实际上没有我想象的那么伪。在行尾使用分号,将“is child”拼写为==0 并使用execwait 的适当变体,它编译为有效C 并在第二次尝试中完成了这项工作。两个wait 调用是必要的。
          • 首先,感谢您的帮助。其次,我还是卡住了嘿嘿。我真的不确定为什么它不起作用。
          • “卡住”怎么办?您是否尝试过我发布的示例代码?它确实有效(转换为 C,请参阅我的第一条评论),我在发布后检查了它。你有没有把这个概念应用到你的代码库中?
          • 问题是我有 N 个管道。所以每次我进入 forkAndExecute 方法时,我只会分叉一次。我无法理解如何保存和使用旧文件描述符。
          • JB 能否请您详细说明“需要两次等待电话”?
          【解决方案6】:

          第一个建议:符号常量比幻数更好。

          const int PIPE_READ = 0;
          const int PIPE_WRITE = 1;
          int fd[2];
          pipe(fd);
          // Now you can refer to fd[PIPE_READ] and fd[PIPE_WRITE].
          

          第二个建议:退后一步,想想你想要完成什么。

          您想生成两个进程,第一个进程的标准输出连接到第二个进程的标准输入。对吧?

          因此,在 C 中,这意味着您需要调用 pipe,将 fd[PIPE_WRITE] 传递给第一个子进程,然后将 dup2 传递给 1,并将 fd[PIPE_READ] 传递给第二个子进程,这会将dup2 设为 0。

          只看forkAndExecute's 的原型就知道它做不到:

          void forkAndExecute( char* arrayOfWords[] , vector *vectorOfPIDs , 
              bool hasNextCmd , bool hasPrevCmd);
          

          它只处理一个命令,并且通过查看该参数列表,除非它求助于邪恶的全局变量,否则它无法从其 PrevCmd 接收文件描述符或从其 NextCmd 接收文件描述符。

          考虑如何管理您需要的文件描述符,并重新设计forkAndExecute 以便能够使用这些。

          【讨论】:

          • 我一直在自我安慰。我会把你的建议铭记在心......退后一步,一步一步地做这件事,不要让自己不知所措。
          【解决方案7】:

          您将每个程序的输入连接到它自己的输出。您可能希望将每个程序的输出连接到下一个程序的输入。

          您应该从basis of two 开始并从那里扩展,而不是使用管道中n 个进程的一般情况。如果您继续扩展工作代码而不是直接针对复杂结构进行拍摄,您将更好地了解文件描述符相互插入的方式。

          【讨论】:

          • 我认为,一旦一个管道的方法完成,您总是可以使用递归处理多个管道。
          • 对我来说,你将如何递归实现这一点并不明显,但为什么不呢。让它有点棘手的是,在创建管道本身之前,我们无法生成pr|pr 模式的任何一个进程。在多管道设置中,这延伸到:在创建输入和输出管道之前,我们不能生成任何进程。
          【解决方案8】:

          好吧,我没有答案,但我正在处理同样的问题。我会分享我所拥有的。它适用于这两个命令,但运行完成后,i/o 被破坏。以一种奇怪的方式,我还无法弄清楚。打电话给水管工!

          void pipeCommand(char** cmd1, char** cmd2) {
            int fds[2]; // file descriptors
            pipe(fds);
            int oldIn, oldOut;
            // child process #1
            if (fork() == 0) {
              // Reassign stdin to fds[0] end of pipe.
              oldIn = dup(STDIN_FILENO);
              dup2(fds[0], STDIN_FILENO);
              close(fds[1]);
              close(fds[0]);
              // Execute the second command.
              execvp(cmd2[0], cmd2);
            // child process #2
            } else if ((fork()) == 0) {
              oldOut = dup(STDOUT_FILENO);
              // Reassign stdout to fds[1] end of pipe.
              dup2(fds[1], STDOUT_FILENO);
              close(fds[0]);
              close(fds[1]);
              // Execute the first command.
              execvp(cmd1[0], cmd1);
            // parent process
            } else
              wait(NULL);
              dup2(oldIn, STDIN_FILENO);
              dup2(oldOut, STDOUT_FILENO);
              close(oldOut);
              close(oldIn);
          }
          

          我觉得这与 wait() 之后我在做什么或不做什么有关

          【讨论】:

          • 您的 oldIn/oldOut 变量既无用又可能导致您陷入困境。您在父进程中关闭它们,但只在子进程中为它们分配值。您的编译器可能将它们默认为 0,实际上您正在永久关闭标准输入 (fd 0)。此外,您需要在wait 之前关闭父进程中的管道;否则读取器进程可能会阻塞,等待来自仍然打开的另一端的数据。
          【解决方案9】:

          好的,这对我有用。希望这对您有所帮助:

          /************************
          function: void pipeCommand(char** cmd1, char** cmd2)
          comment: This pipes the output of cmd1 into cmd2.
          **************************/
          void pipeCommand(char** cmd1, char** cmd2) {
            int fds[2]; // file descriptors
            pipe(fds);
            // child process #1
            if (fork() == 0) {
              // Reassign stdin to fds[0] end of pipe.
              dup2(fds[0], STDIN_FILENO);
              close(fds[1]);
              close(fds[0]);
              // Execute the second command.
              // child process #2
              if (fork() == 0) {
                  // Reassign stdout to fds[1] end of pipe.
                  dup2(fds[1], STDOUT_FILENO);
                  close(fds[0]);
                  close(fds[1]);
                  // Execute the first command.
                  execvp(cmd1[0], cmd1);
              }
              wait(NULL);
              execvp(cmd2[0], cmd2);
              }
              close(fds[1]);
              close(fds[0]);
              wait(NULL);
          }
          

          【讨论】: