dup2() 的用法

Usage of dup2()

dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
if (fd > 2)
  close(fd);

根据《UNIX环境下的高级程序设计》一书,上面的if语句是必须的。这本书建议通过考虑如果 fd = 1 在一种情况下会发生什么而在另一种情况下如果 fd = 3 会发生什么来解决这个问题。

如果 fd = 1,0 (stdin) 将被关闭然后指向 stdout,1 仍将指向 stdout(因为 dup2() 不会关闭文件描述符,如果它等于第一个参数, return 1) 和 2 将指向标准输出。

如果fd = 3,每个文件描述符将首先关闭,然后指向任何文件3指向的文件。

为什么大于2的描述符需要关闭?

dup2 调用的目的是将第一个文件描述符复制到第二个。因此在对 dup2 的三个调用之后,文件描述符 0、1 和 2 被打开并且是文件描述符 fd 的副本。随后对 close 的调用将关闭原始文件描述符。

如果您在调用 close(fd) 之前没有检查 fd > 2,那么您将关闭刚刚打开的文件描述符之一。例如,如果 fd 为 2,则 close(fd)close(2) 相同。

打开一个文件描述符而不对它做任何事情后立即关闭它是不行的。这就是为什么检查是必要的。

重点是大于2不平仓fd;相反,如果它是标准流之一,则不关闭 fd。在您的示例中,关闭 1 (stdout) 显然是不正确的。

但是额外的文件描述符确实需要关闭。当可执行文件启动时,它有权相信只有 fds 0、1 和 2 在使用,因此在 execing 之前清理 -- close -- fds 很重要。 close-on-exec 标志是另一个有用的选项。

您不想让任何未使用的文件描述符保持打开状态。将 stdinstdoutstderr 重定向到 fd 后,您不打算单独使用 fd -- 它只是暂时打开的,所以然后您可以将所有标准描述符复制到它。所以你需要关闭它。

但如果它实际上与 stdinstdoutstderr 相同,则您不想关闭它(这应该不太可能,但如果其中之一是可能的在您打开 fd 之前它们已经关闭)。所以你需要 if 来防止这种情况。

至于为什么关闭原始描述符很重要,某些类型的流仅在关闭引用它的 所有 描述符时才会做一些特殊的(而且通常很重要)。例如,一个 TCP 连接只有在所有描述符都关闭后才会关闭,一个被删除的文件只有在所有链接都被移除并且所有描述符都被关闭时才会从磁盘中删除它的数据,而管道的读取端只有在以下情况下才会收到 EOF所有引用写端的描述符都已关闭。因此,如果您让 fd 打开,然后关闭标准描述符,它将阻止此最终操作的发生。

您需要关闭描述符的一种非常常见的情况是当您创建管道与子进程通信时。代码通常类似于:

pipe(fds);
switch (fork()) {
    case 0: // child
        dup2(fds[0], STDIN_FILENO);
        close(fds[0]);
        close(fds[1]);
        execlp("program", "program", "arg1", (char*)NULL);
        break;
    case -1: // error
        perror("fork");
        exit(-1);
        break;
    default: // parent
        close(fds[0]);
        // Code that writes to fds[1]
}

子进程需要关闭管道的写端(fds[1]),否则父进程关闭时子进程收不到EOF