【问题标题】:Does this multiple pipes code in C makes sense?C 中的这个多管道代码有意义吗?
【发布时间】:2010-10-31 04:45:35
【问题描述】:

我创建了一个question about this a few days。我的解决方案与已接受答案中建议的内容一致。但是,我的一个朋友提出了以下解决方案:

请注意,代码已经更新了几次(检查编辑修订)以反映以下答案中的建议。如果您打算给出一个新的答案,请记住这个新代码,而不是有很多问题的旧代码。

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

int main(int argc, char *argv[]){
    int fd[2], i, aux, std0, std1;

    do {
        std0 = dup(0); // backup stdin
        std1 = dup(1); // backup stdout

        // let's pretend I'm reading commands here in a shell prompt
        READ_COMMAND_FROM_PROMPT();

        for(i=1; i<argc; i++) {
            // do we have a previous command?
            if(i > 1) {
                dup2(aux, 0);
                close(aux);
            }

            // do we have a next command?
            if(i < argc-1) {
                pipe(fd);

                aux = fd[0];
                dup2(fd[1], 1);
                close(fd[1]);
            }

            // last command? restore stdout...
            if(i == argc-1) {
                dup2(std1, 1);
                close(std1);
            }

            if(!fork()) {
                // if not last command, close all pipe ends
                // (the child doesn't use them)
                if(i < argc-1) {
                    close(std0);
                    close(std1);
                    close(fd[0]);
                }

                execlp(argv[i], argv[i], NULL);
                exit(0);
            }
        }

        // restore stdin to be able to keep using the shell
        dup2(std0, 0);
        close(std0);
    }

    return 0;
}

这模拟了一系列通过管道的命令,如 bash,例如:cmd1 |命令2 | ... | cmd_n。我说“模拟”,因为如您所见,命令实际上是从参数中读取的。只是为了节省时间编写一个简单的 shell 提示符...

当然,还有一些问题需要修复和添加,例如错误处理,但这不是重点。我想我有点明白代码,但它仍然让我很困惑这整个事情是如何运作的。

我是否遗漏了什么,或者这确实有效,并且它是解决问题的好而干净的解决方案?如果没有,谁能指出这段代码存在的关键问题?

【问题讨论】:

  • 这段代码应该做什么?
  • 抱歉,我已经编辑了帖子以反映对代码的简单描述。
  • 我开始认为您需要退后一步,将其拆分为各个组成部分,以清晰的功能拆分,而不是将其全部构建为一个不断增长的 hack。一旦您可以识别出一些合适的抽象和不变量,逻辑就会变得更加清晰。再一次,我相信我的 patch-tag.com/r/xsh 简单的 shell 示例是一个很好的模型。
  • 就像我之前说的,你的 shell 比我需要的要多。我已经很困惑了……

标签: c exec fork pipe dup2


【解决方案1】:

这是我的“最终”代码,带有 ehemient 建议:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
#include <string.h>
#include <readline/readline.h>
#include <readline/history.h>

#define NUMPIPES 5
#define NUMARGS 10

int main(int argc, char *argv[]) {
    char *bBuffer, *sPtr, *aPtr = NULL, *pipeComms[NUMPIPES], *cmdArgs[NUMARGS];
    int newPipe[2], oldPipe[2], pCount, aCount, i, status;
    pid_t pid;

    using_history();

    while(1) {
        bBuffer = readline("\e[1;31mShell \e[1;32m# \e[0m");

        if(!strcasecmp(bBuffer, "exit")) {
            return 0;
        }

        if(strlen(bBuffer) > 0) {
            add_history(bBuffer);
        }

        sPtr = bBuffer;
        pCount = -1;

        do {
            aPtr = strsep(&sPtr, "|");

            if(aPtr != NULL) {
                if(strlen(aPtr) > 0) {
                    pipeComms[++pCount] = aPtr;
                }
            }
        } while(aPtr);

        cmdArgs[++pCount] = NULL;

        for(i = 0; i < pCount; i++) {
            aCount = -1;

            do {
                aPtr = strsep(&pipeComms[i], " ");

                if(aPtr != NULL) {
                    if(strlen(aPtr) > 0) {
                        cmdArgs[++aCount] = aPtr;
                    }
                }
            } while(aPtr);

            cmdArgs[++aCount] = NULL;

            // do we have a next command?
            if(i < pCount-1) {
                pipe(newPipe);
            }

            pid = fork();

            if(pid == 0) {
                // do we have a previous command?
                if(i > 0) {
                    close(oldPipe[1]);
                    dup2(oldPipe[0], 0);
                    close(oldPipe[0]);
                }

                // do we have a next command?
                if(i < pCount-1) {
                    close(newPipe[0]);
                    dup2(newPipe[1], 1);
                    close(newPipe[1]);
                }

                // execute command...
                execvp(cmdArgs[0], cmdArgs);
                exit(1);
            } else {
                // do we have a previous command?
                if(i > 0) {
                    close(oldPipe[0]);
                    close(oldPipe[1]);
                }

                // do we have a next command?
                if(i < pCount-1) {
                    oldPipe[0] = newPipe[0];
                    oldPipe[1] = newPipe[1];
                }

                // wait for last command process?
                if(i == pCount-1) {
                    waitpid(pid, &status, 0);
                }
            }
        }
    }

    return 0;
}

现在可以了吗?

【讨论】:

  • 我没有运行它来确定,但它看起来不错。 Little nits:在oldPipe = newPipe 之前取出条件不会有什么坏处; if (i == pcount - 1) 可能只是之前的 ifelse,但我会将它完全移出循环。但是,这些不会改变程序的运行方式;他们更多的是风格问题。
  • 第二点你是对的,它更有意义,'if' 是不必要的。但是整个等待代码无论如何都需要调整,因为我从不等待他以前的所有命令,这样它们就会变成僵尸。但对于这个练习真的不重要。不过,第一点,我只在上面的答案中遵循了您的伪代码。但是如果我删除条件,我不会保留在最后一个命令中打开的 2 个文件描述符吗?
  • 这只是数字的分配。当您执行最后一个命令时,不会使用 newPipe[] 中的实际文件描述符。这真的只是个人口味……没有必要,但我喜欢消除条件。
【解决方案2】:

看起来很合理,虽然它确实需要修复泄漏stdaux 到孩子和循环之后,并且父母的原始stdin 永远丢失了。

颜色可能会更好......

./a.out foo bar baz stdout 标准 = dup(标准输出) || |+===========================std || || || 管道(fd) || ||管道1[0] -- 管道0[1] || || || || || || 辅助 = fd[0] || ||辅助 || || || XX || || || || /--------++---------+| || dup2(fd[1], 1) || // || || || || || || || || 关闭(fd[1]) || || || XX || || || || || 叉+执行(foo)|| || || || XX || || || /-----++-------+| || dup2(aux, 0) // || || || || || || || 关闭(辅助)|| || XX || || || || 管道(fd) || ||管道2[0] -- 管道2[1] || || || || || || 辅助 = fd[0] || ||辅助 || || || XX || || || || /--------++---------+| || dup2(fd[1], 1) || // || || || || || || || || 关闭(fd[1]) || || || XX || || || || || 叉+执行(酒吧)|| || || || XX || || || /-----++-------+| || dup2(aux, 0) // || || || || || || || 关闭(辅助)|| || XX || || || || 管道(fd) || ||管道3[0] -- 管道3[1] || || || || || || 辅助 = fd[0] || ||辅助 || || || XX || || || || /--------++---------+| || dup2(fd[1], 1) || // || || || || || || || || 关闭(fd[1]) || || || XX || || XX || || || /--------++-----------------+| dup2(std, 1) || // || || || || || || fork+exec(baz) || || || ||
  • foo 得到 stdin=stdin, stdout=pipe1[1]
  • bar 得到 stdin=pipe1[0], stdout=pipe2[1]
  • baz 得到 stdin=pipe2[0], stdout=stdout

我的建议不同,它避免破坏父母的stdinstdout,只在孩子内部操纵它们,并且从不泄漏任何FD。不过,绘制图表有点困难。

for cmd in cmds
    if there is a next cmd
        pipe(new_fds)
    fork
    if child
        if there is a previous cmd
            dup2(old_fds[0], 0)
            close(old_fds[0])
            close(old_fds[1])
        if there is a next cmd
            close(new_fds[0])
            dup2(new_fds[1], 1)
            close(new_fds[1])
        exec cmd || die
    else
        if there is a previous cmd
            close(old_fds[0])
            close(old_fds[1])
        if there is a next cmd
            old_fds = new_fds
父母 cmds = [foo, bar, baz] fds = {0:标准输入,1:标准输出} cmd = cmds[0] { 有一个下一个 cmd { 管道(new_fds) new_fds = {3, 4} fds = {0:标准输入,1:标准输出,3:管道1 [0],4:管道1 [1]} } 叉子 => 孩子 有一个下一个 cmd { 关闭(new_fds[0]) fds = {0:标准输入,1:标准输出,4:管道1 [1]} dup2(new_fds[1], 1) fds = {0:标准输入,1:管道1 [1],4:管道1 [1]} 关闭(new_fds[1]) fds = {0:标准输入,1:管道1 [1]} } 执行(命令) 有一个下一个 cmd { old_fds = new_fds old_fds = {3, 4} } } cmd = cmds[1] { 有一个下一个 cmd { 管道(new_fds) new_fds = {5, 6} fds = {0:标准输入,1:标准输出,3:管道1 [0],4:管道1 [1], 5:管道2[0],6:管道2[1]} } 叉子 => 孩子 有一个以前的 cmd { dup2(old_fds[0], 0) fds = {0:管道1 [0],1:标准输出, 3:管道1[0],4:管道1[1], 5:管道2[0],6:管道2[1]} 关闭(old_fds[0]) fds = {0:管道1 [0],1:标准输出, 4:管道1[1], 5:管道2[0] 6:管道2[1]} 关闭(old_fds[1]) fds = {0:管道1 [0],1:标准输出, 5:管道2[0],6:管道2[1]} } 有一个下一个 cmd { 关闭(new_fds[0]) fds = {0: pipe1[0], 1: 标准输出, 6: pipe2[1]} dup2(new_fds[1], 1) fds = {0: pipe1[0], 1: pipe2[1], 6: pipe2[1]} 关闭(new_fds[1]) fds = {0: pipe1[0], 1: pipe1[1]} } 执行(命令) 有一个以前的 cmd { 关闭(old_fds[0]) fds = {0:标准输入,1:标准输出,4:管道1 [1], 5:管道2[0],6:管道2[1]} 关闭(old_fds[1]) fds = {0:标准输入,1:标准输出,5:管道2 [0],6:管道2 [1]} } 有一个下一个 cmd { old_fds = new_fds old_fds = {3, 4} } } cmd = cmds[2] { 叉子 => 孩子 有一个以前的 cmd { dup2(old_fds[0], 0) fds = {0:管道2 [0],1:标准输出, 5:管道2[0],6:管道2[1]} 关闭(old_fds[0]) fds = {0:管道2 [0],1:标准输出, 6:管道2[1]} 关闭(old_fds[1]) fds = {0: pipe2[0], 1: 标准输出} } 执行(命令) 有一个以前的 cmd { 关闭(old_fds[0]) fds = {0:标准输入,1:标准输出,6:管道2 [1]} 关闭(old_fds[1]) fds = {0:标准输入,1:标准输出} } }

编辑

您更新的代码确实修复了以前的 FD 泄漏……但添加了一个:您现在正在向孩子泄漏std0。正如 Jon 所说,这对大多数程序来说可能并不危险......但你仍然应该编写一个比这更好的 shell。

即使它是暂时的,我强烈建议不要修改你自己的 shell 的标准 in/out/err (0/1/2),只在 exec 之前的孩子中这样做。为什么?假设您在中间添加了一些printf 调试,或者由于错误情况需要退出。如果您不首先清理混乱的标准文件描述符,您将遇到麻烦。请,为了让事情即使在意外情况下也能按预期运行,在需要之前不要乱搞。


编辑

正如我在其他 cmets 中提到的,将其拆分为更小的部分会更容易理解。这个小助手应该易于理解且没有错误:

/* cmd, argv: passed to exec
 * fd_in, fd_out: when not -1, replaces stdin and stdout
 * return: pid of fork+exec child
 */
int fork_and_exec_with_fds(char *cmd, char **argv, int fd_in, int fd_out) {
    pid_t child = fork();
    if (fork)
        return child;

    if (fd_in != -1 && fd_in != 0) {
        dup2(fd_in, 0);
        close(fd_in);
    }

    if (fd_out != -1 && fd_in != 1) {
        dup2(fd_out, 1);
        close(fd_out);
    }

    execvp(cmd, argv);
    exit(-1);
}

应该这样:

void run_pipeline(int num, char *cmds[], char **argvs[], int pids[]) {
    /* initially, don't change stdin */
    int fd_in = -1, fd_out;
    int i;

    for (i = 0; i < num; i++) {
        int fd_pipe[2];

        /* if there is a next command, set up a pipe for stdout */
        if (i + 1 < num) {
            pipe(fd_pipe);
            fd_out = fd_pipe[1];
        }
        /* otherwise, don't change stdout */
        else
            fd_out = -1;

        /* run child with given stdin/stdout */
        pids[i] = fork_and_exec_with_fds(cmds[i], argvs[i], fd_in, fd_out);

        /* nobody else needs to use these fds anymore
         * safe because close(-1) does nothing */
        close(fd_in);
        close(fd_out);

        /* set up stdin for next command */
        fd_in = fd_pipe[0];
    }
}

你可以看到Bashexecute_cmd.c#execute_disk_commandexecute_cmd.c#execute_pipeline调用,xshprocess.c#process_runjobs.c#job_run调用,甚至每个BusyBoxvarious调用small and minimal shells 将它们分开。

【讨论】:

  • 确实......虽然,我仍然认为这很令人困惑,哈哈,但我想我现在明白了它是如何工作的。是的,我们知道它缺少一些 closes(),可能没有你们指出的那么多。我们也知道丢失的标准输入,我发现返回外壳时出现分段错误的困难方式。我必须说实话,虽然很短暂,但我无法理解你的解决方案。我尝试并试图理解它,但无法理解,所以我使用您的代码中的一些提示以自己的方式尝试了它。我将在下面的新答案中发布我的完整代码...
  • 我已经更新了第一个答案中的代码几次,我想我在最后一个答案中得到了它。请你看一下,看看我是否遗漏了什么重要的东西?
  • 我现在明白你所说的不在父级中进行这些更改。就像我说的,这是我朋友的做法,虽然我不知道为什么,但我对它不太满意。我忘了发布我自己的代码,将在几秒钟内在下面的答案中完成......
  • 我正在(再次)查看您自己的解决方案的“图表”,我不确定,但我认为您有错误。在第二个命令迭代中,在父级中,检查下一个命令,如果有,则设置 old_fds = new_fds。 old_fds 不应该等于 {5, 6} 而不是 {3, 4} 吗?
  • 我想我现在可以使用您的解决方案,一切都很有意义。特地看了几遍那个“图”。 :) 不过,等待您对上一个的回答,我相信这只是您的一个错字,我想确定一下。
【解决方案3】:

在这个问题和另一个问题中(如第一篇文章中所链接),ephemient 建议我解决问题,而不会弄乱父文件描述符,正如这个问题中的可能解决方案所证明的那样.

我没有得到他的解决方案,我尝试并试图理解,但我似乎无法理解。我也尝试在不理解的情况下对其进行编码,但它没有用。可能是因为我没有正确理解它并且无法对其进行编码,它应该被编码。

无论如何,我尝试使用我从伪代码中理解的一些东西来提出自己的解决方案,并提出了这个:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
#include <string.h>
#include <readline/readline.h>
#include <readline/history.h>

#define NUMPIPES 5
#define NUMARGS 10

int main(int argc, char *argv[]) {
    char *bBuffer, *sPtr, *aPtr = NULL, *pipeComms[NUMPIPES], *cmdArgs[NUMARGS];
    int aPipe[2], bPipe[2], pCount, aCount, i, status;
    pid_t pid;

    using_history();

    while(1) {
        bBuffer = readline("\e[1;31mShell \e[1;32m# \e[0m");

        if(!strcasecmp(bBuffer, "exit")) {
            return 0;
        }

        if(strlen(bBuffer) > 0) {
            add_history(bBuffer);
        }

        sPtr = bBuffer;
        pCount =0;

        do {
            aPtr = strsep(&sPtr, "|");

            if(aPtr != NULL) {
                if(strlen(aPtr) > 0) {
                    pipeComms[pCount++] = aPtr;
                }
            }
        } while(aPtr);

        cmdArgs[pCount] = NULL;

        for(i = 0; i < pCount; i++) {
            aCount = 0;

            do {
                aPtr = strsep(&pipeComms[i], " ");

                if(aPtr != NULL) {
                    if(strlen(aPtr) > 0) {
                        cmdArgs[aCount++] = aPtr;
                    }
                }
            } while(aPtr);

            cmdArgs[aCount] = NULL;

            // Do we have a next command?
            if(i < pCount-1) {
                // Is this the first, third, fifth, etc... command?
                if(i%2 == 0) {
                    pipe(aPipe);
                }

                // Is this the second, fourth, sixth, etc... command?
                if(i%2 == 1) {
                    pipe(bPipe);
                }
            }

            pid = fork();

            if(pid == 0) {
                // Is this the first, third, fifth, etc... command?
                if(i%2 == 0) {
                    // Do we have a previous command?
                    if(i > 0) {
                        close(bPipe[1]);
                        dup2(bPipe[0], STDIN_FILENO);
                        close(bPipe[0]);
                    }

                    // Do we have a next command?
                    if(i < pCount-1) {
                        close(aPipe[0]);
                        dup2(aPipe[1], STDOUT_FILENO);
                        close(aPipe[1]);
                    }
                }

                // Is this the second, fourth, sixth, etc... command?
                if(i%2 == 1) {
                    // Do we have a previous command?
                    if(i > 0) {
                        close(aPipe[1]);
                        dup2(aPipe[0], STDIN_FILENO);
                        close(aPipe[0]);
                    }

                    // Do we have a next command?
                    if(i < pCount-1) {
                        close(bPipe[0]);
                        dup2(bPipe[1], STDOUT_FILENO);
                        close(bPipe[1]);
                    }
                }

                execvp(cmdArgs[0], cmdArgs);
                exit(1);
            } else {
                // Do we have a previous command?
                if(i > 0) {
                    // Is this the first, third, fifth, etc... command?
                    if(i%2 == 0) {
                        close(bPipe[0]);
                        close(bPipe[1]);
                    }

                    // Is this the second, fourth, sixth, etc... command?
                    if(i%2 == 1) {
                        close(aPipe[0]);
                        close(aPipe[1]);
                    }
                }

                // wait for the last command? all others will run in the background
                if(i == pCount-1) {
                    waitpid(pid, &status, 0);
                }

                // I know they will be left as zombies in the table
                // Not relevant for this...
            }
        }
    }

    return 0;
}

这可能不是最好和最干净的解决方案,但这是我能想到的,最重要的是,我能理解。让我不理解的东西工作,然后我被老师评估,我无法向他解释代码在做什么,这有什么好处?

不管怎样,你觉得这个怎么样?

【讨论】:

    【解决方案4】:

    关键问题是您创建了一堆管道并且没有确保所有末端都正确关闭。如果你创建一个管道,你会得到两个文件描述符;如果你分叉,那么你有四个文件描述符。如果您将dup()dup2() 管道的一端设置为标准描述符,则需要关闭管道的两端 - 至少其中一个关闭必须在 dup() 或 dup2() 操作之后。


    考虑第一个命令可用的文件描述符(假设至少有两个 - 通常应该处理的东西(不需要 pipe() 或只需要一个命令的 I/O 重定向),但我认识到错误消除了处理以保持代码适合 SO):

        std=dup(1);    // Likely: std = 3
        pipe(fd);      // Likely: fd[0] = 4, fd[1] = 5
        aux = fd[0];
        dup2(fd[1], 1);
        close(fd[1]);  // Closes 5
    
        if (fork() == 0) {
             // Need to close: fd[0] aka aux = 4
             // Need to close: std = 3
             close(fd[0]);
             close(std);
             execlp(argv[i], argv[i], NULL);
             exit(1);
        }
    

    请注意,因为fd[0] 在子进程中没有关闭,所以子进程永远不会在其标准输入上获得 EOF;这通常是有问题的。 std 的非封闭性不太重要。


    重新审视修改后的代码(截至 2009-06-03T20:52-07:00)...

    假设进程以文件描述符 0、1、2(标准输入、输出、错误)仅打开开始。还假设我们正好有 3 个命令要处理。和以前一样,这段代码用注释写出循环。

    std0 = dup(0); // backup stdin - 3
    std1 = dup(1); // backup stdout - 4
    
    // Iteration 1 (i == 1)
    // We have another command
    pipe(fd);   // fd[0] = 5; fd[1] = 6
    aux = fd[0]; // aux = 5
    dup2(fd[1], 1);
    close(fd[1]);       // 6 closed
    // Not last command
    if (fork() == 0) {
        // Not last command
        close(std1);    // 4 closed
        close(fd[0]);   // 5 closed
        // Minor problemette: 3 still open
        execlp(argv[i], argv[i], NULL);
        }
    // Parent has open 3, 4, 5 - no problem
    
    // Iteration 2 (i == 2)
    // There was a previous command
    dup2(aux, 0);      // stdin now on read end of pipe
    close(aux);        // 5 closed
    // We have another command
    pipe(fd);          // fd[0] = 5; fd[1] = 6
    aux = fd[0];
    dup2(fd[1], 1);
    close(fd[1]);      // 6 closed
    // Not last command
    if (fork() == 0) {
        // Not last command
        close(std1);   // 4 closed
        close(fd[0]);  // 5 closed
        // As before, 3 is still open - not a major problem
        execlp(argv[i], argv[i], NULL);
        }
    // Parent has open 3, 4, 5 - no problem
    
    // Iteration 3 (i == 3)
    // We have a previous command
    dup2(aux, 0);      // stdin is now read end of pipe 
    close(aux);        // 5 closed
    // No more commands
    
    // Last command - restore stdout...
    dup2(std1, 1);     // stdin is back where it started
    close(std1);       // 4 closed
    
    if (fork() == 0) {
        // Last command
        // 3 still open
        execlp(argv[i], argv[i], NULL);
    }
    // Parent has closed 4 when it should not have done so!!!
    // End of loop
    // restore stdin to be able to keep using the shell
    dup2(std0, 0);
    // 3 still open - as desired
    

    因此,所有的孩子都将原始标准输入连接为文件描述符 3。这并不理想,尽管它不是可怕的创伤;我很难找到一个重要的情况。

    在父级中关闭文件描述符 4 是一个错误 - '读取命令并处理它的下一次迭代将不起作用,因为 std1 未在循环内初始化。

    一般来说,这接近正确 - 但并不完全正确。

    【讨论】:

    • 就像我说的那样,我们知道一些缺失的关闭,但不是那些在孩子身上的......但有一件事,关闭 fd[0] 与在孩子中关闭 aux 相同?
    • 根据您的所有建议,我尝试修复第一篇文章中的“伪”代码,请您看看新代码,看看我是否遗漏了什么?
    • 以防你看到我几分钟前发布的编辑后的代码,我再次更新了它,我认为现在更好了。
    • 因为 aux 是 fd[0] 的副本,所以传递给 close() 的两者中的哪一个无关紧要——同一个文件描述符将被关闭。
    • @Nazgulled:我查看了最新的代码——我认为没关系(尽管“do”循环最后没有“while”——它仍然是伪代码)。我没有像以前那样对代码进行正式的书面执行。你应该这样做是为了让自己确信它是正确的。
    【解决方案5】:

    它会给出一些意想不到的结果。这远非一个好的解决方案:它与父进程的标准描述符混淆,不恢复标准输入,描述符泄漏给子进程,等等。

    如果你递归地思考,它可能更容易理解。下面是一个正确的解决方案,没有错误检查。考虑一个链表类型command,它是next 指针和一个argv 数组。

    void run_pipeline(command *cmd, int input) {
      int pfds[2] = { -1, -1 };
    
      if (cmd->next != NULL) {
        pipe(pfds);
      }
      if (fork() == 0) { /* child */
        if (input != -1) {
          dup2(input, STDIN_FILENO);
          close(input);
        }
        if (pfds[1] != -1) {
          dup2(pfds[1], STDOUT_FILENO);
          close(pfds[1]);
        }
        if (pfds[0] != -1) {
          close(pfds[0]);
        }
        execvp(cmd->argv[0], cmd->argv);
        exit(1);
      }
      else { /* parent */
        if (input != -1) {
          close(input);
        }
        if (pfds[1] != -1) {
          close(pfds[1]);
        }
        if (cmd->next != NULL) {
          run_pipeline(cmd->next, pfds[0]);
        }
      }
    }
    

    用链表中的第一个命令调用它,input = -1。剩下的就交给它了。

    【讨论】:

    • 其实我觉得这比上面我已经看懂的代码还要混乱。不过感谢您的意见。
    • 但有一件事,如果我像在更新的代码中那样恢复它们,那么会不会弄乱父描述符?如果您还没有这样做,请查看新代码。
    • 你应该再看看我的代码,它并没有那么复杂。 ;是的,它仍然很糟糕。 fork+exec 的全部意义在于您可以在forkexec 调用之间“准备房子”(描述符、权限、功能、环境变量等)。在父级中做所有事情会使原本简单的事情变得过于复杂。
    • 我不相信递归地解释它比迭代地解释它更好,但是...... +1 用于强调这些点,或者至少尝试这样做。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-18
    • 2014-04-17
    • 1970-01-01
    • 1970-01-01
    • 2011-10-19
    相关资源
    最近更新 更多