理解dup和dup2

时间:2017-04-10 05:13:21

标签: c linux io-redirection

我正在尝试了解Linux中的重定向,我发现这个代码创建了一个子进程并重定向输入/输出。但在这种情况下,我无法理解dup和dup2正在做什么。我知道dup使用编号最小的未使用描述符作为新描述符。任何人都可以解释这段代码如何帮助多重定向?

int run_child(char *progname, char *argv[], int child_stdin, int child_stdout, int child_stderr)
{
    int child;

    if ((child = fork()))
    {
        return child;
    }

    if (child_stdout == STDIN_FILENO)
    {
        child_stdout = dup(child_stdout);
        RC_CHECK(child_stdout >= 0);
    }

    while (child_stderr == STDIN_FILENO || child_stderr == STDOUT_FILENO)
    {
        child_stderr = dup(child_stderr);
        RC_CHECK(child_stderr >= 0);
    }

    child_stdin = dup2(child_stdin, STDIN_FILENO);
    RC_CHECK(child_stdin == STDIN_FILENO);
    child_stdout = dup2(child_stdout, STDOUT_FILENO);
    RC_CHECK(child_stdout == STDOUT_FILENO);
    child_stderr = dup2(child_stderr, STDERR_FILENO);
    RC_CHECK(child_stderr == STDERR_FILENO);

    execvp(progname, argv);
}

1 个答案:

答案 0 :(得分:1)

我认为代码正在尝试,但有时会失败,以解决以下情况:

  • 父进程还关闭了三个标准流中的部分或全部(标准输入,标准输出和标准错误),因此部分或全部文件描述符01和{{ 1}}被关闭了。
  • 父进程已打开三个文件描述符,供孩子用作标准输入,标准输出和标准错误。

有几个子场景:

  • 2已分配到child_stdout0)。可以想象STDIN_FILENO被分配到child_stderr1)。
  • STDOUT_FILENO被分配到child_stderr0 - 可能意味着1没有被分配到其中任何一个。
  • child_stdoutchild_stdout都未分配到child_stderr01

代码叉;父代码立即返回 - 这一切都是干净的。它在失败时返回2,在成功时返回PID。

对于子方案1,第一个条件运行-1,它将dup()的赋值更改为可用的child_stdout之后的第一个未打开的文件描述符。这可能是01或更大的数字。 它会丢弃有关2最初的信息。

然后,循环会尝试确保child_stdout不是child_stderr0,再次丢弃有关原始内容的信息。

下一个序列三个调用可确保当前1中的文件描述符复制到child_stdinSTDIN_FILENO复制到child_stdoutSTDOUT_FILENO }被复制到child_stderr。由于STDERR_FILENO没有关闭原始文件描述符,如果它与要复制的文件描述符相同(但不是这样),则最终会出现合理的连接。

但是,在正常情况下,输入参数是(例如)dup2()35,代码不能确保这些参数被关闭。 这是一个错误。

然后代码继续使用7执行命令。它不处理失败的情况;它简单地从函数的末尾掉落,导致未定义的行为,因为该函数应该返回一个值。如果execvp()函数系列中的任何函数返回,则表示失败。代码应该报告错误消息并退出,可能使用exec*()或者使用“快速退出”(exit()_exit()或类似的东西)。

如何解决?

我认为代码应该保留子文件描述符的原始值,并准备好在_Exit()序列后关闭它们,如果它们超出范围dup2(),{ {1}},0。除此之外,代码可能处理大多数情况。

请注意,重定向标准错误后报告错误是非常困难的。下面的代码简单地写出了当前标准错误,这是最好的,除非你再次使用1或类似的系统。

2

这不是简单的代码;你的思绪被各种可能性所摧毁。 (我已经删除了各种特殊情况,在进一步检查时,在编写我的分析时结果并不特别。)我不相信用syslog修复的预检是值得的; #include <assert.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define RC_CHECK(test) assert((test) != 0) int run_child(char *progname, char *argv[], int child_stdin, int child_stdout, int child_stderr); int run_child(char *progname, char *argv[], int child_stdin, int child_stdout, int child_stderr) { int child; if ((child = fork())) { return child; } int fd[3] = { child_stdin, child_stdout, child_stderr }; if (child_stdout == STDIN_FILENO) { child_stdout = dup(child_stdout); RC_CHECK(child_stdout >= 0); } while (child_stderr == STDIN_FILENO || child_stderr == STDOUT_FILENO) { child_stderr = dup(child_stderr); RC_CHECK(child_stderr >= 0); } child_stdin = dup2(child_stdin, STDIN_FILENO); RC_CHECK(child_stdin == STDIN_FILENO); child_stdout = dup2(child_stdout, STDOUT_FILENO); RC_CHECK(child_stdout == STDOUT_FILENO); child_stderr = dup2(child_stderr, STDERR_FILENO); RC_CHECK(child_stderr == STDERR_FILENO); for (int i = 0; i < 3; i++) { if (fd[i] != STDIN_FILENO && fd[i] != STDOUT_FILENO && fd[i] != STDERR_FILENO) close(fd[i]); } execvp(progname, argv); /* Or: fprintf(stderr, "Failed to execute program %s\n", progname); */ char *msg[] = { "Failed to execute program ", progname, "\n" }; enum { NUM_MSG = sizeof(msg) / sizeof(msg[0]) }; for (int i = 0; i < NUM_MSG; i++) write(2, msg[i], strlen(msg[i])); exit(1); /* Or an alternative status such as 126 or 127 based on errno */ } 的作者可以简单地规定所有三个文件描述符dup()run_child()0都是打开的,以便所有子文件描述符参数都大于{{1并简单地继续I / O重定向。当然,仍然需要关闭传递给函数的文件描述符。