1

我一直在制作执行管道的简单外壳。

这是一些用于操作管道语法的代码。

int fd[2];
int stdin_copy;
int stdout_copy;

int status;
char * msg;

if (pipe(fd) == -1) {
    perror("pipe");
    exit(1);
}
// fd[0] : process read from fd[0]
// fd[1] : process write to fd[1]

if (execok(pr_words) == 0) { /* is it executable? */
    status = fork(); /* yes; create a new process */

    if (status == -1) { /* verify fork succeeded */
        perror("fork");
        exit(1);
    } else if (status == 0) { /* in the child process... */
        stdout_copy = dup(1);

        close(1); // close standard output        
        dup(fd[1]);
        close(fd[0]);
        close(fd[1]); // close and fd[1] will be stdout 
        pr_words[l_nwds] = NULL; /* mark end of argument array */

        status = execve(path, pr_words, environ); /* try to execute it */

        perror("execve"); /* we only get here if */
        exit(0); /* execve failed... */
    }
    /*------------------------------------------------*/
    /* The parent process (the shell) continues here. */
    /*------------------------------------------------*/
    else if (status > 0) { // in the parent process....
        wait( & status); /* wait for process to end */

        if (execok(af_words) == 0) {
            if (pipe(fd2) == -1) {
                perror("pipe");
                exit(1);
            }

            status = fork();

            if (status == -1) {
                perror("fork");
                exit(1);
            } else if (status == 0) { // in the child process...
                stdin_copy = dup(0);
                close(0);
                dup(fd[0]);

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

                read(fd[0], readbuffer, sizeof(readbuffer));

                af_words[r_nwds] = NULL; /* mark end of argument array */
                status = execve(path, af_words, environ); /* try to execute it */

            } else if (status > 0) {
                wait( & status);

                msg = "over";
                write(2, msg, strlen(msg));
                close(fd[0]);
                close(fd[1]);
                dup2(stdin_copy, 0);
                dup2(stdout_copy, 1);
                close(stdin_copy);
                close(stdout_copy);
                printf("%s", "hi");
            }
        } else {
            /*----------------------------------------------------------*/
            /* Command cannot be executed. Display appropriate message. */
            /*----------------------------------------------------------*/
            msg = "*** ERROR: '";
            write(2, msg, strlen(msg));
            write(2, af_words[0], strlen(af_words[0]));
            msg = "' cannot be executed.\n";
            write(2, msg, strlen(msg));
        }

    }

} else {
    /*----------------------------------------------------------*/
    /* Command cannot be executed. Display appropriate message. */
    /*----------------------------------------------------------*/
    msg = "*** ERROR: '";
    write(2, msg, strlen(msg));
    write(2, pr_words[0], strlen(pr_words[0]));
    msg = "' cannot be executed.\n";
    write(2, msg, strlen(msg));
}

pr_words 和 af_words 是包含命令、管道右侧和左侧的二维指针。(例如 ls | cat -> pr_words = "ls\0" ,af_words = "cat\0")

而且,首先我使用 fork() 创建子进程并将 fd[1] 注册为标准输出。(并在关闭标准输入之前保存标准输入文件描述符)并在执行命令左侧之后,使其他子进程用于处理命令右侧。

同样,我在关闭标准输出之前保存了标准输出文件描述符并制作了 fd[0] 标准输入。通过使用 execve 函数的第一个结果的输入,我认为每个结果都将保存在 fd[1] 中。(因为这目前已注册为标准输出)。

最后,将管道输入和输出恢复为标准输出。(我不想使用 dup2 但由于我缺乏知识我别无选择)

但是,在执行此代码后,我输入了 'ls | cat',没有输出。此外,我设置终端的每个条目都将打印'#'。(这意味着'#ls'或'#cat'......)但是,在输入上述管道命令后,该程序甚至不打印'#'。

我猜这个程序的输入和输出流在处理完管道命令后完全扭曲了。

我该如何解决?我的意思是,我想将第一个 execve 的结果保存到 fd[1] 中,并在使用这个 fd[1] 执行第二个 execve 之后,将通过 stdout 文件描述打印最终结果。

4

1 回答 1

1

我至少看到您的代码存在一些问题:

首先,在开始第二个进程之前,您不应该在第一个进程上 wait() 。管道中只有几 KB 的缓冲区,之后如果第一个子进程尝试继续在那里写入,您的 shell 将挂起。您需要在为每个孩子等待()之前启动两个孩子。只需将第一个 wait(&status) 调用移到另一个旁边。您可能希望稍后使用 waitpid 或其他东西,以便知道哪个先完成以及哪个状态进入哪个状态,但是一旦您掌握了基础知识,您就可以解决这个问题。

其次,当你 fork() 时,程序中的所有变量和文件描述符映射都会被复制。因此,您不需要在任一子进程中保存标准输入或标准输出,因为您在子进程中所做的任何更改都不会影响父进程。此外,由于您仅在子进程中初始化 stdin_copy 和 stdout_copy,因此您在第二个 fork() 之后在父进程中使用的那些变量的版本未初始化。这就是在执行此代码后导致父 shell 的 I/O 混乱的原因。在第二次分叉后,您实际上不需要在父级中做任何事情来维护原始的标准输入和标准输出 - 在此之前您永远不会在该过程中更改它们。您可能想从 post-fork 父代码中删除所有这些:

            close(fd[0]);
            close(fd[1]);
            dup2(stdin_copy, 0);
            dup2(stdout_copy, 1);
            close(stdin_copy);
            close(stdout_copy);

第三,为什么在第二个孩子调用 execve() 之前从管道中读取?那只是将数据从您的执行孩子永远不会看到的管道中剥离出来。这可能是导致管道本身无法正常工作的原因。你可能想删除这个:

read(fd[0], readbuffer, sizeof(readbuffer));    

最后,这一行可能需要在 execok() 调用之前进行(对于另一个类似的调用也是如此):

pr_words[l_nwds] = NULL; /* mark end of argument array */

代码的骨架应该看起来像这样,省略错误处理和 execok 检查,如果你想知道哪个状态码是哪个孩子的,则演示 waitpid() 的使用:

int child_pid[2];
child_pid[0] = fork();
if (child_pid[0] == 0) {
    // first child, close stdout and replace with pipe, then exec
} else {
    child_pid[1] = fork();
    if (child_pid[1] == 0) {
        // second child, close stdin and replace with pipe, then exec
    } else {
        // parent, and now we have the pids of the children
        waitpid(child_pid[0], &status, 0); // wait for first child
        waitpid(child_pid[1], &status, 0); // wait for second child
        // *do not* mess with stdin/stdout, they are okay here
    }
}
于 2016-04-07T08:15:35.393 回答