dup2 / dup - 为什么要复制文件描述符?

102

我在尝试理解 dup2dup 的用法。

根据 man 手册:

DESCRIPTION

dupdup2 创建了文件描述符 oldfd 的副本。成功返回后,旧和新的描述符可以互换使用。它们共享锁、文件位置指针和标志;例如,如果在其中一个描述符上使用 lseek 修改文件位置,则也会为另一个描述符更改位置。

然而,这两个描述符不共享 close-on-exec 标志。 dup 在新描述符中使用最低未使用编号的描述符。

dup2 使 newfd 成为 oldfd 的副本,必要时关闭 newfd

RETURN VALUE

dupdup2 返回新的描述符,如果发生错误则返回-1(此时,errno 会适当设置)。

我为什么需要这个系统调用呢?复制文件描述符有什么用处?如果我已经有了文件描述符,为什么还想要创建它的一个副本呢?能否给我解释并且举例说明何时需要使用 dup2/dup


你如何在没有使用 dupdup2 的情况下实现 shell 的管道功能?你需要调用 pipe(2),然后将其中一个文件描述符 dup 到例如 STDIN_FILENO - Basile Starynkevitch
1
可能是实际示例使用dup或dup2的重复问题。 - DrTyrsa
4个回答

52

dup 系统调用可以复制一个已有的文件描述符,返回一个新的文件描述符,它指向相同的底层 I/O 对象。

Dup 允许 shell 实现像这样的命令:

ls existing-file non-existing-file > tmp1  2>&1

2>&1 会告诉 shell 给命令分配一个文件描述符 2,这个描述符是描述符 1 的副本。(即 stderr 和 stdout 指向同一 fd)。
现在,在调用不存在的文件时,ls 命令会输出错误信息,并且在使用存在的文件时,ls 命令会输出正确的结果,并且将它们都存储在 tmp1 文件中。

下面这个例子代码会运行带有标准输入连接到管道的读取端的程序 wc。

int p[2];
char *argv[2];
argv[0] = "wc";
argv[1] = 0;
pipe(p);
if(fork() == 0) {
    close(STDIN); //CHILD CLOSING stdin
    dup(p[STDIN]); // copies the fd of read end of pipe into its fd i.e 0 (STDIN)
    close(p[STDIN]);
    close(p[STDOUT]);
    exec("/bin/wc", argv);
} else {
    write(p[STDOUT], "hello world\n", 12);
    close(p[STDIN]);
    close(p[STDOUT]);
}

子进程使用dup将读取端复制到文件描述符0,关闭p中的文件描述符,并执行wc。当wc从标准输入读取时,它从管道中读取。
这就是使用dup实现管道的方式,也是dup的其中一种用法。现在你可以使用管道来构建其他东西,这就是系统调用的美妙之处:你可以使用已经存在的工具来构建下一个工具,而这些工具又是由其他东西构建而成的,如此往复。
最终,系统调用是内核中最基本的工具。

干杯 :)


1
Sodup对调用者有帮助,而不是ls程序本身?如果已经可以访问文件,那么在像ls这样的程序中使用dup是否有任何好处?例如,在这里,ls将错误写入硬编码为2的位置,因此作为ls的消费者,我有一种覆盖它的方法。我认为这是一个微妙的点,不是吗? - Nishant
3
你的示例程序似乎存在一个错误;你调用了dup(p[STDIN]),但却抛弃了结果。你是不是想使用dup2(p[STDIN], 0)?请注意,这种更改会影响程序的行为,因此请确保在进行更改之前进行适当的测试。 - Quuxplusone
1
@Quuxplusone 的 dup 函数返回“进程当前未使用的最低编号描述符”。由于 fd 0 刚刚关闭,dup 应该返回 0。而 dup2 显式指定要使用哪个 fd,而不是仅使用最低空闲 fd,因此我更喜欢使用 dup2 - Wodin
@Wodin:啊,我敢打赌你说的对,OP当时肯定是这么想的。但是我也正确吗?"刚关闭"是相对的,如果有并发线程正在打开文件,那么OP的代码可能会出问题,对吧? - Quuxplusone
1
@Wodin JFI:使用dup2时,您不需要在之前调用close - blueyed
显示剩余2条评论

20

复制文件描述符的另一个原因是与 fdopen 一起使用。 fclose 关闭传递给 fdopen 的文件描述符,因此如果您不希望关闭原始文件描述符,则必须首先使用 dup 对其进行复制。


fdopen() 似乎不会复制文件描述符,它只是在用户空间创建缓冲区。 - Eric
4
你误读了我的回答。重点在于,在将文件描述符传递给fdopen之前,您可能希望复制该描述符以防fclose关闭它。 - R.. GitHub STOP HELPING ICE
如果您不想关闭文件描述符,为什么要调用 fclose 呢?您是在谈论那种您无法控制的库函数调用 fclose 的情况吗?在调用库函数之前,您会调用 dup 以保持文件描述符的打开状态吗? - theferrit32
1
如果您通过stdio接口分配一个FILE句柄来访问一个预先存在的打开文件,则需要调用fclose来释放该FILE句柄。如果您想继续使用底层的打开文件,或者如果您的软件架构使原始的文件描述符“所有者”代码将其关闭,则fclose也会关闭您传递给fdopen的底层文件描述符的事实是一个问题。您可以通过使用dup为相同的打开文件创建一个新的文件描述符以传递给fdopen来避免此问题,以便fclose不会关闭原始文件描述符。 - R.. GitHub STOP HELPING ICE
3
重点是fdopen()会移动 fd 的所有权到 FILE,而不是复制它。这是用户需要注意的事情。需要保留可用的fd句柄和FILE对象的使用者必须复制该fd。就是这样。 - Conrad Meyer
1
@ConradMeyer:是的,这是一个非常好的表述方式,需要注意的是,一旦您将所有权移交给FILE,就没有“移动所有权”的操作。 - R.. GitHub STOP HELPING ICE

4

请注意与 dup/dup2 相关的一些要点

dup/dup2 - 技术上的目的是通过不同的句柄在 单个进程 中共享一个文件表项。(如果我们在分叉,描述符默认在子进程中被复制,并且文件表项也被共享)。

这意味着我们可以使用 dup/dup2 函数拥有一个以上的文件描述符,可能具有不同的属性,来表示一个单独的打开文件表项。

(虽然目前似乎只有 FD_CLOEXEC 标志是文件描述符的唯一属性)。

http://www.gnu.org/software/libc/manual/html_node/Descriptor-Flags.html

dup(fd) is equivalent to fcntl(fd, F_DUPFD, 0);

dup2(fildes, fildes2); is equivalent to 

   close(fildes2);
   fcntl(fildes, F_DUPFD, fildes2);

除了dup2和fcntl之间的一些errno值之外,差异在于关闭后跟随fcntl可能会引发竞争条件,因为涉及两个函数调用。
详细信息可以从以下链接中检查: http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html 一个使用示例 - 在实现shell中的作业控制时,可以看到使用dup / dup2的有趣示例..在下面的链接中。

http://www.gnu.org/software/libc/manual/html_node/Launching-Jobs.html#Launching-Jobs


4

dup 用于重定向进程的输出。

例如,如果您想保存进程的输出,您需要复制输出(fd=1),将复制后的fd重定向到文件,然后fork和执行进程,当进程结束时,再次将保存的fd重定向到输出。


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