基础信息:
- 所需头文件:
#include <unistd.h>
- 函数原型:
int dup2(int oldfd, int newfd);
- 返回值:成功返回新的文件描述符,失败返回-1
- 功能:将
oldfd
描述符复制给newfd
深入理解:
当我们把oldfd
和newfd
传入到dup2
时,到底发生了什么呢?
这里需要用到有关文件方面的知识了,作一个简要的说明(具体请参考我之前的博客http://blog.youkuaiyun.com/move_now/article/details/62054836):
内核在表示一个打开的文件时使用了三个数据结构,文件描述符表,文件表以及v节点表。
1. 文件描述符表:每个进程都独有的一张用于存储文件描述符标志和指向文件表某一项的文件指针
2. 文件表:每一项都代表了一个打开的文件,存储的是文件的状态标志以及当前文件的偏移量还有指向v节点表的指针(注意同一文件可以被打开多次)
3. v节点表:存储了v节点的信息(文件类型以及对此文件进行操作的各种函数的指针)和i节点。i节点是在打开文件时从磁盘上读入内存的,里面存储了文件的大量信息,我们平时使用的ls -l
命令读取的信息大部分就是从i节点里面提取出来的。
知道了这些,其实我们调用dup2
之后,相当于newfd
的文件指针被oldfd
的文件指针覆盖了,这样造成的结果就是两者指向同一个文件表项。于是它们共享同一文件的状态标志、偏移量还有指向相同的v节点表以及各种该文件的信息。这就是为什么调用了dup2
之后,两个文件描述符都可以用来访问同一个文件的原因。
细节部分:
接下来有几个问题,如果弄懂了那证明就已经理解了:
问题一:当我们使用dup2时,oldfd
是否需要处于打开状态呢?
答案是需要,因为只有打开的文件才会在文件表中占有一项,它的文件指针才有作用,这样赋给newfd
才有意义。
问题二:newfd
是否需要处于打开状态呢?
回答是不需要,就算打开了,也会先被关闭(除非newfd
和oldfd
是相同的文件描述符)。dup2
完成的功能相当于是先调用close
关闭newfd
,然后再调用fcntl(oldfd, F_DUPFD, newfd)
,不过dup2
是原子操作。
问题三:既然我们传入处于打开状态的newfd
会被先关闭掉,那么调用了dup2
之后如果再打开一个新的文件,那么newfd
是否会被用于打开的新的文件的描述符呢?
结果是不会,newfd
索引到的文件描述符表非空,是处于被占用状态的,所以不会分配给新的文件。
这里为了加深印象,给出一段测试代码:
#include <iostream>
using namespace std;
#include <unistd.h>
#include <fcntl.h>
#include <string>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
char s[] = "123123";
int fd = open("test.txt", O_RDWR | O_CREAT, 0644);
cout << "fd :" << fd << endl;
//fd会先被close掉,不过是在dup2内部完成的
dup2(STDOUT_FILENO, fd);
//这里打开了一个新的文件,但是输出发现占用的是fd之后的文件描述符
int fd1 = open("test1.txt", O_RDWR | O_CREAT, 0644);
cout << "fd1:" << fd1 << endl;
if(write(fd, s, sizeof(s)) < 0)
{
perror("write error:");
exit(1);
}
close(fd);
close(fd1);
return 0;
}
题外话:
之前提到了一下文件描述符标志和文件的状态标志。这两者的不同之处其实根据所存储它们的数据结构就可以得到这样的结论:文件描述符标志是仅仅针对文件描述符的,当用另外的文件描述符索引到同一个文件时,该标志在新的文件描述符中就不见了。而文件的状态标志则是针对每个文件的,不管索引到该文件的文件描述符是多少。当我们调用了dup2
或者dup
之后,文件描述符标志中的FD_CLOEXEC
标志会被清除掉。FD_CLOEXEC
标志被设置的作用是当我们调用exec
函数簇的时候,对应的设置了该标志的文件描述符会在执行exec
指定的文件前被关闭。
具体是什么情况由于和我们的目的不同,就不在这里阐述了。(当我们调用exec
之后,不相让exec
执行的程序访问原来进程打开的文件描述符所指向的文件时,就应该设置该标志)。