标题：dup2
函数简介
函数名： dup2
功能： 复制文件句柄
用法： int dup2(int oldhandle,int newhandle);
程序例：
#include <sys\stat.h>
#include <string.h>
#include <fcntl.h>
#include <io.h>
int main(void)
{
#define STDOUT 1
int nul,oldstdout;
char msg[] = "This is a test";
/* create a file */
nul = open("DUMMY.FIL",O_CREAT | O_RDWR | 
S_IREAD | S_IWRITE);
/* create a duplicate handle for standard
output */
oldstdout = dup(STDOUT);
/*
redirect standard output to DUMMY.FIL
by duplicating the file handle onto the
file handle for standard output.
*/
dup2(nul,STDOUT);
/* close the handle for DUMMY.FIL */
close(nul);
/* will be redirected into DUMMY.FIL */
write(STDOUT,msg,strlen(msg));
/* restore original standard output
handle */
dup2(oldstdout,STDOUT);
/* close duplicate handle for STDOUT */
close(oldstdout);
return 0;
}
书目摘抄
下面内容选自《UNⅨ环境高级编程》
Stevens said:
⑴ 每个进程在进程表中都有一个记录项，每个记录项中有一张打开文件描述符表，可将视为一个矢量，每个描述符占用一项。与每个文件描述符相关联的是：
（a) 文件描述符标志。
（b) 指向一个文件表项的指针。
⑵ 内核为所有打开文件维持一张文件表。每个文件表项包含：
（a) 文件状态标志（读、写、增写、同步、非阻塞等）。
（b) 当前文件位移量。
（c) 指向该文件v节点表项的指针。
图示：
文件描述符表
------------
fd0 0 | p0 -------------> 文件表0 ---------> vnode0
------------
fd1 1 | p1 -------------> 文件表1 ---------> vnode1
------------
fd2 2 | p2
------------
fd3 3 | p3
------------
... ...
... ...
------------一、单个进程内的dup和dup2假设进程A拥有一个已打开的文件描述符fd3，它的状态如下：
进程A的文件描述符表（before dup2）
------------
fd0 0 | p0
------------
fd1 1 | p1 -------------> 文件表1 ---------> vnode1
------------
fd2 2 | p2
------------
fd3 3 | p3 -------------> 文件表2 ---------> vnode2
------------
... ...
... ...
------------
经下面调用：
n_fd = dup2(fd3,STDOUT_FILENO）；后进程状态如下：
进程A的文件描述符表（after dup2）
------------
fd0 0 | p0
------------
n_fd 1 | p1 ------------
------------ \
fd2 2 | p2 \
------------ _\|
fd3 3 | p3 -------------> 文件表2 ---------> vnode2
------------
... ...
... ...
------------
解释如下：
n_fd = dup2(fd3,STDOUT_FILENO）表示n_fd与fd3共享一个文件表项（它们的文件表指针指向同一个文件表项），n_fd在文件描述符表中的位置为 STDOUT_FILENO的位置，而原先的STDOUT_FILENO所指向的文件表项被关闭，我觉得上图应该很清晰的反映出这点。按照上面的解释我们就可以解释CU中提出的一些问题：
⑴ "dup2的第一个参数是不是必须为已打开的合法filedes？" -- 答案：必须。
⑵ "dup2的第二个参数可以是任意合法范围的filedes值么？" -- 答案：可以，在Unix其取值区间为[0,255]。
另外感觉理解dup2的一个好方法就是把fd看成一个结构体类型，就如上面图形中画的那样，我们不妨把之定义为：
struct fd_t {
int index;
filelistitem *ptr;
};
然后dup2匹配index，修改ptr，完成dup2操作。
在学习dup2时总是碰到“重定向”一词，上图完成的就是一个“从标准输出到文件的重定向”，经过dup2后进程A的任何目标为STDOUT_FILENO的I/O操作如printf等，其数据都将流入fd3所对应的文件中。下面是一个例子程序：
#define TESTSTR "Hello dup2\n"
int main() {
int fd3;
fd3 = open("testdup2.dat",0666）；
if (fd < 0) {
printf("open error\n");
exit(-1）；
}
if (dup2(fd3,STDOUT_FILENO) < 0) {
printf("err in dup2\n");
}
printf(TESTSTR);
return 0;
}
其结果就是你在testdup2.dat中看到"Hello dup2"。二，重定向后恢复CU上有这样一个帖子，就是如何在重定向后再恢复原来的状态？首先大家都能想到要保存重定向前的文件描述符。那么如何来保存呢，象下面这样行么？
int s_fd = STDOUT_FILENO;
int n_fd = dup2(fd3,STDOUT_FILENO);
还是这样可以呢？
int s_fd = dup(STDOUT_FILENO);
int n_fd = dup2(fd3,STDOUT_FILENO);
这两种方法的区别到底在哪呢？答案是第二种方案才是正确的，分析如下：按照第一种方法，我们仅仅在"表面上"保存了相当于fd_t（按照我前面说的理解方法）中的index，而在调用dup2之后，ptr所指向的文件表项由于计数值已为零而被关闭了，我们如果再调用dup2(s_fd,fd3）就会出错（出错原因上面有解释）。而第二种方法我们首先做一下复制，复制后的状态如下图所示：
进程A的文件描述符表（after dup)
------------
fd0 0 | p0
------------
fd1 1 | p1 -------------> 文件表1 ---------> vnode1
------------ /|
fd2 2 | p2 /
------------ /
fd3 3 | p3 -------------> 文件表2 ---------> vnode2
------------ /
s_fd 4 | p4 ------/
------------
... ...
... ...
------------
调用dup2后状态为：
进程A的文件描述符表（after dup2）
------------
fd0 0 | p0
------------
n_fd 1 | p1 ------------
------------ \
fd2 2 | p2 \
------------ _\|
fd3 3 | p3 -------------> 文件表2 ---------> vnode2
------------
s_fd 4 | p4 ------------->；文件表1 ---------> vnode1
------------
... ...
... ...
------------
dup(fd）的语意是返回的新的文件描述符与fd共享一个文件表项。就如after dup图中的s_fd和fd1共享文件表1一样。
确定第二个方案后重定向后的恢复就很容易了，只需调用dup2(s_fd,n_fd）；即可。下面是一个完整的例子程序：
#define TESTSTR "Hello dup2\n"
#define SIZEOFTESTSTR 11
int main() {
int fd3;
int s_fd;
int n_fd;
fd3 = open("testdup2.dat",0666）；
if (fd3 < 0) {
printf("open error\n");
exit(-1）；
}
/* 复制标准输出描述符 */
s_fd = dup(STDOUT_FILENO);
if (s_fd < 0) {
printf("err in dup\n");
}
/* 重定向标准输出到文件 */
n_fd = dup2(fd3,STDOUT_FILENO);
if (n_fd < 0) {
printf("err in dup2\n");
}
write(STDOUT_FILENO,TESTSTR,SIZEOFTESTSTR); /* 写入testdup2.dat中 */
/* 重定向恢复标准输出 */
if (dup2(s_fd,n_fd) < 0) {
printf("err in dup2\n");
}
write(STDOUT_FILENO,TESTSTR,SIZEOFTESTSTR); /* 输出到屏幕上 */
return 0;
}
注意这里我在输出数据的时候我是用了不带缓冲的write库函数，如果使用带缓冲区的printf，则最终结果为屏幕上输出两行"Hello dup2"，而文件testdup2.dat中为空，原因就是缓冲区作怪，由于最终的目标是屏幕，所以程序最后将缓冲区的内容都输出到屏幕。三、父子进程间的dup/dup2由fork调用得到的子进程和父进程的相同文件描述符共享同一文件表项，如下图所示：
父进程A的文件描述符表
------------
fd0 0 | p0
------------
fd1 1 | p1 -------------> 文件表1 ---------> vnode1
------------ /|\
fd2 2 | p2 |
------------ |
|
子进程B的文件描述符表 |
------------ |
fd0 0 | p0 |
------------ |
fd1 1 | p1 ---------------------|
------------
fd2 2 | p2
------------
所以恰当的利用dup2和dup可以在父子进程之间建立一条“沟通的桥梁”。这里不详述。四、小结灵活的利用dup/dup2可以给你带来很多强大的功能，花了一些时间总结出上面那么多，不知道自己理解的是否透彻，只能在以后的实践中慢慢探索了。