linux下dup/dup2函数的用法

系统调用dup和dup2能够复制文件描述符。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
   ------------
... ...
... ...
   ------------

一、单个进程内的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\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);
        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\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);
        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可以给你带来很多强大的功能,花了一些时间总结出上面那么多,不知道自己理解的是否透彻,只能在以后的实践中慢慢探索了。

时间: 2024-10-21 23:21:45

linux下dup/dup2函数的用法的相关文章

dup/dup2函数学习

dup/dup2函数用来实现文件描述符之间的拷贝.对此,先来看看函数的声明: #include <unistd.h> int dup(int oldfd); int dup2(int oldfd, int newfd); dup函数 dup函数传入一个文件描述符,oldfd必须是已打开的文件描述符,否则dup函数调用失败.返回值为当前系统可用的最小文件描述符.测试程序如下: #include <stdio.h> #include <string.h> #include

linux下转格式函数iconv段错误

今天将windows代码移植到Linux下,其中用到了Unicode转char的函数,被坑了一会,相关函数及编码格式,Linux与windows不同,有几点需要注意: 1.wchar_t 在Linux下占用4个字节,在windows下占2个字节: 2.Linux默认的文本编码方式是UTF-8:Linux终端汉字显示的设置方式:vi /etc/sysconfig/i18n: 设置LANG="en_US.UTF-8"或者LANG="zh_CN.UTF-8": 3.ico

笔记3-6: dup/dup2函数

dup与dup2函数 用于复制现存的文件描述符. 原型: #include <unistd.h> int dup(int fd); int dup2(int fd, int fd2); 两函数若成功则返回新描述符,出错则返回-1. dup函数返回一个新的描述符,并且这个新描述符一定是可用描述符中数值最小的一个. dup2函数使用fd2参数指定的数值返回新描述符,如果fd2已经打开,则先关闭fd2. 笔记3-6: dup/dup2函数,布布扣,bubuko.com

linux下的信号处理函数总结

1.信号处理函数 相关函数原型如下: #include <signal.h> sighandler_t signal(int signum, sighandler_t handler); 第一参数是信号 第二个参数是信号处理器:             1.可以是SIG_DFL,信号的默认动作             2. 可以是SIG_IGN,忽略该信号             3. 一个带有一个整型参数的处理函数. #include <signal.h> int sigacti

linux下C语言函数执行时间统计

转载:http://blog.csdn.net/linquidx/article/details/5916701#t5 写好程序,用gcc编译,带上-pg参数,然后运行以后分析gmon.out文件: 命令exp:   gprof ./test-main ./gmon.out >1.log  在1.log中会生成各函数运行情况. gprof 1.1 简介 gprof实际上只是一个用于读取profile结果文件的工具.gprof采用混合方法来收集程序的统计信息,他使用检测方法,在编译过程中在函数入口

Linux下利用ioctl函数获取网卡信息

linux下的ioctl函数原型如下: #include <sys/ioctl.h> int ioctl(int handle, int cmd, [int *argc, int argv]) 函数成功返回0,失败返回-1. 其相关命令接口如下: 类别 Request 说明 数据类型 套 接 口 SIOCATMARK SIOCSPGRP SIOCGPGRP 是否位于带外标记 设置套接口的进程ID 或进程组ID 获取套接口的进程ID 或进程组ID int int int 文 件 FIONBIO

Linux下利用signal函数处理ctrl+c等信号

前言 linux下可以通过信号机制来实现程序的软中断,是一个非常有用的编程方法.我们平时在程序运行的时候按下ctrl-c.ctrl-z或者kill一个进程的时候其实都等效于向这个进程发送了一个特定信号,当进程捕获到信号后,进程会被中断并立即跳转到信号处理函数.默认情况下一个程序对ctrl-c发出的信号(SIGINT)的处理方式是退出进程,所以当我们按下ctrl-c的时候就可以终止一个进程的运行. signal函数 但是有时候我们希望我们的程序在被信号终止之前执行一些特定的收尾流程,或者我们希望我

Linux下使用system()函数一定要谨慎 【转载】

出处:http://blog.csdn.net/kyokowl/article/details/8823334 C/C++]Linux下使用system()函数一定要谨慎 曾经的曾经,被system()函数折磨过,之所以这样,是因为对system()函数了解不够深入.只是简单的知道用这个函数执行一个系统命令,这远远不够,它的返回值.它所执行命令的返回值以及命令执行失败原因如何定位,这才是重点.当初因为这个函数风险较多,故抛弃不用,改用其他的方法.这里先不说我用了什么方法,这里必须要搞懂syste

dup/dup2函数

原子操作(automic operation)指的是由多步组成的操作.如果该操作原子的执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集. 向打开文件时设置O_APPEND标志的文件写入是原子操作.内核每次对这种文件进行写之前,都将进程的当前偏移量设置到该文件的尾端处. O_EXCL | O_CREAT 是一个原子操作.如果文件已存在,则出错.否则,创建文件. dup/dup2两个函数都用于复制一个现存的文件描述符: #include<unistd.h> int du