【问题标题】:Understand dup and dup2了解 dup 和 dup2
【发布时间】:2017-09-05 00:55:07
【问题描述】:

我正在尝试了解 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);
}

【问题讨论】:

    标签: c linux io-redirection


    【解决方案1】:

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

    • 父进程还关闭了部分或全部三个标准流(标准输入、标准输出和标准错误),因此部分或全部文件描述符012 已关闭。
    • 父进程打开了三个文件描述符,作为子进程的标准输入、标准输出和标准错误。

    有几个子场景:

    • child_stdout 被分配给 0 (STDIN_FILENO)。可以想象,child_stderr 被分配到了1 (STDOUT_FILENO)。
    • child_stderr 被分配到 01 — 大概意思是 child_stdout 没有被分配到其中任何一个。
    • child_stdoutchild_stderr 均未分配给 012

    代码分叉;父代码立即返回——这一切都很干净。它在失败时返回 -1,在成功时返回 PID。

    对于子场景 1,第一个条件运行 dup(),它将 child_stdout 的分配更改为 0 之后可用的第一个未打开文件描述符。这可能是12 或更大的数字。 它会丢弃有关 child_stdout 最初是什么的信息。

    然后循环尝试确保child_stderr 既不是0 也不是1,再次丢弃有关它最初是什么的信息。

    接下来的三个调用确保当前在child_stdin 中的文件描述符被复制到STDIN_FILENOchild_stdout 被复制到STDOUT_FILENOchild_stderr 被复制到STDERR_FILENO。由于dup2() 不会关闭与要复制的文件描述符相同的原始文件描述符(否则会关闭),因此最终会产生合理的连接。

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

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

    如何解决?

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

    请注意,在重定向标准错误后报告错误是令人担忧的。下面的代码只是写入当前的标准错误,这是它可以做的最好的事情,除非你使用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() 的编写者可以简单地规定所有三个文件描述符 012 都是打开的,因此所有子文件描述符参数都大于 2 并简单地继续 I/ O 重定向。当然,仍然需要关闭传递给函数的文件描述符。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-09
      • 1970-01-01
      • 1970-01-01
      • 2010-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多