• 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可以在父子进程之间建立一条“沟通的桥梁”。(匿名管道)

  • 相关阅读:
    Flink 1.14 新特性预览
    基于 MaxCompute 的实时数据处理实践
    Serverless 工程实践 | 细数 Serverless 的配套服务
    不得不看!虚拟货币和区块链的关系
    美国华尔街拥抱区块链是最大的威胁
    1.图片底部圆弧
    二、快捷键
    三、ASP.NET Core 部署Linux
    一、.NET Core MVC 项目结构模板
    一、纯css实现顶部进度条随滚动条滚动
  • 原文地址:https://www.cnblogs.com/zhangxuan/p/6273571.html
Copyright © 2020-2023  润新知