zoukankan      html  css  js  c++  java
  • dup的使用

    转自: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
    "
    
    int main() 
    {
        int fd;
    
        fd = open("testdup2.dat", O_CREAT|O_RDWR|O_APPEND, S_IRWXU);
        if (fd < 0) 
        {
            printf("open error
    ");
            exit(-1);
        }
    
        if (dup2(fd, STDOUT_FILENO) < 0)
        {       
            printf("err in dup2
    ");
        }
        printf(TESTSTR);
       close( fd );
    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);即可。下面是一个完整的例子程序:

      
    
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdlib.h>
    
    #define TESTSTR "Hello dup2
    "
    #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 < 0) 
        {
            printf("open error
    ");
            exit(-1);
        }
    
       
        s_fd = dup(STDOUT_FILENO);
        if (s_fd < 0) 
        {
           printf("err in dup
    ");
        }
    
       
        n_fd = dup2(fd, STDOUT_FILENO);
        if (n_fd < 0) 
        {
            printf("err in dup2
    ");
        }
        write(STDOUT_FILENO, TESTSTR, SIZEOFTESTSTR);  
        //also use the following two lines
        //printf(TESTSTR);
        //fflush(stdout);
    
       
        if (dup2(s_fd, n_fd) < 0) 
        {
            printf("err in dup2
    ");
        }
        else
        {
            printf("recover stdout success
    ");
        }
        write(STDOUT_FILENO, TESTSTR, SIZEOFTESTSTR);
        //also
        //printf(TESTSTR);
    
        close( fd );
        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可以在父子进程之间建立一条“沟通的桥梁”。(匿名管道)

  • 相关阅读:
    5-python基础—获取某个目录下的文件列表(适用于任何系统)
    Automated, Self-Service Provisioning of VMs Using HyperForm (Part 1) (使用HyperForm自动配置虚拟机(第1部分)
    CloudStack Support in Apache libcloud(Apache libcloud中对CloudStack支持)
    Deploying MicroProfile-Based Java Apps to Bluemix(将基于MicroProfile的Java应用程序部署到Bluemix)
    Adding Persistent Storage to Red Hat CDK Kit 3.0 (在Red Hat CDK Kit 3.0添加永久性存储)
    Carve Your Laptop Into VMs Using Vagrant(使用Vagran把您笔记本电脑刻录成虚拟机)
    使用Python生成一张用于登陆验证的字符图片
    Jupyter notebook的安装方法
    Ubuntu16.04使用Anaconda5搭建TensorFlow使用环境 图文详细教程
    不同时区的换算
  • 原文地址:https://www.cnblogs.com/zhangxuan/p/6273571.html
Copyright © 2011-2022 走看看