转自:http://www.cnblogs.com/GODYCA/archive/2013/01/05/2846197.html

下面是关于实现重定向的函数dup和dup2的解释:

系统调用dup和dup2能够复制文件描述符。dup 和dup2都是返回新的描述符。或者返回-1并设置 errno变量。新老描述符共享文件的偏移量(位置)、标志和锁,但是不共享close-on-exec标志。

他的原型如下:

#include <unsitd.h>

int dup(int oldfd); 拷贝fd,返回当前系统最小且没有被使用的fd。

int dup2(int oldfd,int newfd);

dup2可以让用户指定返回的文件描述符的值,dup2可以指定拷贝后的newfd,原先的newfd会被关闭 。

(两个常量STDIN_FILENO和STDOUT_FILENO定义在<unistd.h>头文件中,它们指定了标准输入和标准输出的文件描述符。)

int n_fd = dup2(fd, STDOUT_FILENO);

将STDOUT_FILENO重定向到fd,即文件描述符STDOUT_FILENO复制了文件描述符fd,共享fd对应的文件对象。此时任何目标为STDOUT_FILENO的I/O操作,如printf()等数据都会流入fd对应的文件。

如果fd为tcp套接字描述符,则会被发送到与客户端连接的socket上,这就是CGI的实现原理。这就能解释CGI程序中大量的printf()语句

相信大部分在Unix/Linux下编程的程序员手头上都有《Unix环境高级编程》(APUE)这本超级经典巨著。作者在该书中讲解dup/dup2之前曾经讲过“文件共享”,这对理解dup/dup2还是很有帮助的。这里做简单摘录以备在后面的分析中使用:
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所对应的文件中。下面是一个例子程序:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h> #define TESTSTR "Hello dup2\n" int main()
{
int fd; fd = open("testdup2.dat", O_CREAT|O_RDWR|O_APPEND, S_IRWXU);
if (fd < )
{
printf("open error\n");
exit(-);
} if (dup2(fd, STDOUT_FILENO) < )
{
printf("err in dup2\n");
}
printf(TESTSTR);
   close( fd );
return ;
}

其结果就是你在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);即可。下面是一个完整的例子程序:


#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h> #define TESTSTR "Hello dup2\n"
#define SIZEOFTESTSTR 11 int main()
{
int fd;
int s_fd;
int n_fd; fd = open("testdup2.dat", O_CREAT|O_RDWR|O_APPEND, S_IRWXU);
if (fd < )
{
printf("open error\n");
exit(-);
} s_fd = dup(STDOUT_FILENO);
if (s_fd < )
{
printf("err in dup\n");
} n_fd = dup2(fd, STDOUT_FILENO);
if (n_fd < )
{
printf("err in dup2\n");
}
write(STDOUT_FILENO, TESTSTR, SIZEOFTESTSTR);
//also use the following two lines
//printf(TESTSTR);
//fflush(stdout); if (dup2(s_fd, n_fd) < )
{
printf("err in dup2\n");
}
else
{
printf("recover stdout success\n");
}
write(STDOUT_FILENO, TESTSTR, SIZEOFTESTSTR);
//also
//printf(TESTSTR); close( fd );
return ;
}

注 意这里我在输出数据的时候我是用了不带缓冲的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的使用的更多相关文章

  1. 调用0A中断输入字符串数据段的DUP定义

    ;这是自动生成的代码模板 STACKS SEGMENT STACK ;堆栈段 DW DUP(?) ;注意这里只有128个字节 STACKS ENDS DATAS SEGMENT ;数据段 STRING ...

  2. Linux进程间通信(三):匿名管道 popen()、pclose()、pipe()、close()、dup()、dup2()

    在前面,介绍了一种进程间的通信方式:使用信号,我们创建通知事件,并通过它引起响应,但传递的信息只是一个信号值.这里将介绍另一种进程间通信的方式——匿名管道,通过它进程间可以交换更多有用的数据. 一.什 ...

  3. dup和dup2用法小结

    今天和同学探讨了一下关于重定向输出到文件的问题,其中需要用到dup和dup2函数,因此来小小的总结一下. 首先来man一下: dup直接返回一个新的描述符和原来的描述符一样代表同一个资源,描述符的值就 ...

  4. Linux内核分析:dup、dup2的实现

    一.首先需要看一下这两个函数的作用: #include <unistd.h> int dup(int oldfd); int dup2(int oldfd, int newfd); 根据m ...

  5. Linux编程 ---- dup函数

    dup,dup2,dup3函数       顾名思义,dup及duplicate的简写,也就是复制的意思.而事实上这几个函数的功能也确实是复制文件描述符.那为什么要复制文件描述符呢?呵呵,我认为是程序 ...

  6. linux下dup/dup2函数的用法

    系统调用dup和dup2能够复制文件描述符.dup返回新的文件文件描述符(没有用的文件描述符最小的编号).dup2可以让用户指定返回的文件描述符的值,如果需要,则首先接近newfd的值,他通常用来重新 ...

  7. dup和dup2函数

    下面两个函数都可用来复制一个现存的文件描述符: #include<unistd.h> int dup(int filedes); int dup2(int filedes,int file ...

  8. dup和dup2函数以及管道的实现

    疑问:管道应该不是这样实现的,因为这要求修改程序的代码 dup和dup2也是两个非常有用的调用,它们的作用都是用来复制一个文件的描述符.它们经常用来重定向进程的stdin.stdout和stderr. ...

  9. 文件I/O(不带缓冲)之dup和dup2函数

    下面两个函数都可用来复制一个现有的文件描述符: #include <unistd.h> int dup( int filedes ); int dup2( int filedes, int ...

  10. Unix环境高级编程学习笔记——dup

    dup 和 dup2   dup和dup2,都是用来将一个文件描述符复制给另一个文件描述符上,这两个文件描述符都指向同一个文件状态标志上. 只是文件描述符的大小不一样,dup所执行下的复制,肯定是返回 ...

随机推荐

  1. Python3基础 file with 配合文件操作

             Python : 3.7.0          OS : Ubuntu 18.04.1 LTS         IDE : PyCharm 2018.2.4       Conda ...

  2. 乘积尾零|2018年蓝桥杯B组题解析第三题-fishers

    标题:乘积尾零 如下的10行数据,每行有10个整数,请你求出它们的乘积的末尾有多少个零? 5650 4542 3554 473 946 4114 3871 9073 90 4329 2758 7949 ...

  3. 【传输对象】kafka传递实体类消息

    工具类 负责对象字节数组的相互转换,传输数据用 package com.yq.utils; import java.io.ByteArrayInputStream; import java.io.By ...

  4. 51nod 1073约瑟夫环

    思路传送门 :http://blog.csdn.net/kk303/article/details/9629329 n里面挑选m个 可以递推从n-1里面挑m个 然后n-1里面的x 可以转换成 n里面的 ...

  5. HDU 6069 Counting Divisors(唯一分解定理+因子数)

    http://acm.hdu.edu.cn/showproblem.php?pid=6069 题意: 思路: 根据唯一分解定理,$n={a_{1}}^{p1}*{a2_{}}^{p2}...*{a_{ ...

  6. mysql中index与Multiple-Column Indexes区别与联系

    索引对提升SELECT/UPDATE语句查询速度有着立竿见影的效果,有索引和无索引,查询速度往往差几个数量级. 本次讨论一下index(每列作为一个索引,单列索引)和Multiple-Column I ...

  7. Python中通过csv的writerow输出的内容有多余的空行

    第一种方法 如下生成的csv文件会有多个空行 import csv #python2可以用file替代open with open("test.csv","w" ...

  8. python tar 压缩解压

    压缩: 1. import tarfile import os def tar(fname): t = tarfile.open(fname + ".tar.gz", " ...

  9. visudo使用笔记

    目录前言一.介绍二.配置文件简介三.实战配置 前言:    su 的确为管理带来方便,通过切换到root下,能完成所有系统管理工具,只要把root的密码交给任何一个普通用户,他都能切换到root来完成 ...

  10. Beta 冲刺 第三天

    第三天 2018.6.26 今日完成任务情况. 妥志福.牛瑞鑫: 完成任务:让用户接触系统,指出系统中存在的问题,并统计修改的问题,提出修改的方案. 王胜海.马中林: 完成任务:对文档中存在的问题修改 ...