dup,dup2函数的用法


linux下dup|dup2函数的用法

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

他的原型如下:

#include <unsitd.h>

int dup(int oldfd);

int dup2(int oldfd,int newfd);

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

 

相信大部分在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
   ------------
... ...
... ...
   ------------

一、单个进程内的dupdup2
假设进程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_fdfd3共享一个文件表项(它们的文件表指针指向同一个文件表项)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_FILENOI|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);
        return0;
}
其结果就是你在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的文件描述符表(afterdup)
   
------------
fd0 0   | p0
   ------------
fd1 1   | p1-------------> 文件表1 --------->vnode1
   
------------                 ||
fd2 2   |p2                |
   ------------             |
fd3 3   | p3-------------> 文件表2---------> vnode2
   
------------          |
s_fd 4   | p4 ------|
   ------------
... ...
... ...
   ------------

调用dup2后状态为:
进程A的文件描述符表(afterdup2)
   
------------
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_fdfd1共享文件表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);  

       
        
if(dup2(s_fd, n_fd) < 0) {
                printf("err in dup2|n");
        }
        write(STDOUT_FILENO,TESTSTR, SIZEOFTESTSTR);
        return0;
}
意这里我在输出数据的时候我是用了不带缓冲的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
   ------------
所以恰当的利用dup2dup可以在父子进程之间建立一条沟通的桥梁。这里不详述。

四、小结
灵活的利用dup|dup2可以给你带来很多强大的功能,花了一些时间总结出上面那么多,不知道自己理解的是否透彻,只能在以后的实践中慢慢探索了。

整理后的测试源码

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "ourhdr.h"

#define TESTSTR "Hello dup2\n"
#define SIZEOFTESTSTR 11

int main(int argc,char **argv)
{
 int   fd3;
 int   s_fd;
 int   n_fd;
 fd3 = open("testdup2.dat",O_RDWR|O_SYNC);
 if (fd3 < 0) {
  printf("open error\n");
  exit(-1);
 }

 s_fd = dup(STDOUT_FILENO);
 if (s_fd < 0) {
  printf("err in dup\n");
 }
 close(STDOUT_FILENO);

 n_fd = dup2(fd3, STDOUT_FILENO);
 if (n_fd < 0) {
  printf("err in dup2\n");
 }

write(n_fd, TESTSTR, SIZEOFTESTSTR);
write(STDOUT_FILENO, TESTSTR, SIZEOFTESTSTR);
fsync(n_fd);
close(fd3);
close(n_fd);

if (dup2(s_fd, n_fd) < 0) {
  printf("err in dup2\n");
 }
 write(STDOUT_FILENO, TESTSTR, SIZEOFTESTSTR);

//  printf("%d\n",);
//  printf("%s\n",);
  return 0;

}


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值