词条 | 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; } 书目摘抄下面内容选自《UNIX环境高级编程》 Stevens said: (1) 每个进程在进程表中都有一个记录项,每个记录项中有一张打开文件描述符表,可将视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是: (a) 文件描述符标志。 (b) 指向一个文件表项的指针。 (2) 内核为所有打开文件维持一张文件表。每个文件表项包含: (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中提出的一些问题: (1) "dup2的第一个参数是不是必须为已打开的合法filedes?" -- 答案:必须。 (2) "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\" int main() { int fd3; fd3 = open("testdup2.dat", 0666); if (fd < 0) { printf("open error\"); exit(-1); } if (dup2(fd3, STDOUT_FILENO) < 0) { printf("err in dup2\"); } 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\" #define SIZEOFTESTSTR 11 int main() { int fd3; int s_fd; int n_fd; fd3 = open("testdup2.dat", 0666); if (fd3 < 0) { printf("open error\"); exit(-1); } /* 复制标准输出描述符 */ s_fd = dup(STDOUT_FILENO); if (s_fd < 0) { printf("err in dup\"); } /* 重定向标准输出到文件 */ n_fd = dup2(fd3, STDOUT_FILENO); if (n_fd < 0) { printf("err in dup2\"); } write(STDOUT_FILENO, TESTSTR, SIZEOFTESTSTR); /* 写入testdup2.dat中 */ /* 重定向恢复标准输出 */ if (dup2(s_fd, n_fd) < 0) { printf("err in dup2\"); } 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可以给你带来很多强大的功能,花了一些时间总结出上面那么多,不知道自己理解的是否透彻,只能在以后的实践中慢慢探索了。 |
随便看 |
百科全书收录4421916条中文百科知识,基本涵盖了大多数领域的百科知识,是一部内容开放、自由的电子版百科全书。