调用dup/dup2后关闭文件描述符的规则是什么?

7
我觉得这是一个我曾经视为理所当然的话题。过去,我只是“因为被告知”关闭了尽可能多的文件描述符。大多数时候这都有效,但偶尔会遇到一些不可预测的行为。
因此,我想问 - 在调用dup / dup2后关闭文件描述符的规则是什么?
假设我想执行cat < in > out
fd[IN] = open("in", O_RDONLY);
saved_stdin = dup(STDIN_FILENO);
dup2(fd[IN], STDIN_FILENO);
close(fd[IN])

fd[OUT] = open("out", O_WRONLY | O_CREAT | O_TRUNC, 0644);
saved_stdout = dup(STDOUT_FILENO);
dup2(fd[OUT], STDOUT_FILENO);
close(fd[OUT])


// Later on when I want to restore stdin and stdout
dup2(saved_stdin, STDIN_FILENO);
close(saved_stdin);
dup2(saved_stdout, STDINOUT_FILENO);
close(saved_stdout);

这是否正确,或者我应该关闭更多的文件描述符?
2个回答

2

这个规则实际上非常简单。对于两种dup()变体,以下是正确的:

  • 源文件描述符(source fd)保持打开状态,在不再需要时必须关闭它。

  • 目标文件描述符(target fd):

    • 使用dup()时,它始终是未使用的文件描述符。
    • 使��dup2()时,它会被隐式关闭并替换为源文件描述符(source fd)的副本。
  • 新的目标文件描述符在不再需要时必须关闭。

源文件描述符(source fd)是要复制的文件描述符,而目标文件描述符(target fd)是新的文件描述符。

int new_fd = dup(source_fd);
dup2(source_fd, new_fd);

所以,是的,你的代码执行了必要的关闭操作,并且没有多余的操作。


抱歉,您能澄清一下您所说的源文件描述符和目标文件描述符是什么意思吗? - doctopus
我有一个最后的问题 - 连续多次保存stdin/stdout会有什么危害吗?例如:saved_stdin = dup(STDIN_FILENO)。如果我没错的话,这既不关闭saved_stdin也不关闭STDIN_FILENO,我是对的吗? - doctopus
这是正确的,但下面的 dup2() 关闭了先前的 stdin 并将其替换为 fd[IN]。这完全没有问题。 - Ctx
我在想,如果你执行 saved_stdin = dup(STDIN_FILENO) 然后跟着是 dup2(fd[IN], STDIN_FILENO)。现在 STDIN_FILENO 指向 fd[IN]。那么如果你再次调用 saved_stdin = dup(STDIN_FILENO),这不会将原始的 STDIN_FILENO 存储在 saved_stdin 中替换为 fd[IN] 吗?我感觉自己在过度思考或者什么的。 - doctopus
@doctopus 你不应该混淆覆盖“变量”和覆盖“文件描述符”。当然,设置一个变量对于与进程相关联的文件描述符集合没有任何副作用。 - Ctx

0

这些数字来自于CSAPP系统级:

图2: 重定向IO之前

dup2(4,1);

图1:重定向IO后

注意,在调用dup2之后,fd 1refcnt已经变为0。

根据Linux手册close的描述:

如果文件描述符是使用unlink(2)删除的文件的最后一个引用,则该文件将被删除。

close用于减少打开文件的refcnt。我们使用dup创建新的文件描述符会增加打开文件的refcnt。当我们调用close函数时,它不会立即关闭文件,而只会减少文件的引用计数。当refcnt为0时,文件将被关闭/删除。

因此,这确实像内存管理的引用计数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接