dup, dup2一图胜千言

来源:互联网 发布:多角色数据库设计 编辑:程序博客网 时间:2024/06/08 18:18

一图胜千言啊,看了必须懂啊^_^



下面这一段原文:http://os.chinaunix.net/a2009/0602/1049/000001049422_7.shtml  

在C语言里,操纵文件的渠道则是FILE结构,不难想象,C语言中的FILE结构必定和fd有一对一的关系,每个FILE结构都会记录自己唯一对应的fd。

  FILE、fd、打开文件表和打开文件对象的关系如图11-4所示。


     图11-4中,内核指针p指向该进程的打开文件表,所以只要有fd,就可以用fd+p来得到打开文件表的某一项地址。stdin、stdout、stderr均是FILE结构的指针。

  对于Windows中的句柄,与Linux中的fd大同小异,不过Windows的句柄并不是打开文件表的下标,而是其下标经过某种线性变换之后的结果。

  在大致了解了I/O为何物之后,我们就能知道I/O初始化的职责是什么了。首先I/O初始化函数需要在用户空间中建立stdin、stdout、stderr及其对应的FILE结构,使得程序进入main之后可以直接使用printf、scanf等函数。


下面这一段原文:http://www.01happy.com/c-dup-dup2/

C语言中dup和dup2函数的不同和使用

在linux下,通过open打开以文件后,会返回一个文件描述符,文件描述符会指向一个文件表,文件表中的节点指针会指向节点表。看下图:

打开文件的内核数据结构

打开文件的内核数据结构

dup和dup2两个函数都可以用来复制打开的文件描述符,复制成功后和复制源共享同一个文件表。看下图:

执行dup后的内核数据结构

执行dup后的内核数据结构

dup函数

dup(现存的文件描述符)

dup返回的新文件描述符一定是当前可以用描述符中的最小值。下面先打开一个文件来看下文件描述符,为保证测试成功,创建一个测试文件log.txt。

1#include <fcntl.h>
2#include <stdio.h>
3 
4int main(int argc, char *argv[])
5{
6    int fd;
7    fd = open("./log.txt", O_RDWR);
8 
9    printf("%d\n", fd);
10 
11    return 0;
12}

上面的代码用读写打开了log.txt这个文件,编译上面的代码然后执行,执行成功的话,应当是输出3,因为0,1,2分别被标准输入,标准输出,标准错误输出占用了。使用dup复制这个文件描述符,并尝试移动fd偏移量:

1#include <unistd.h>
2#include <fcntl.h>
3#include <stdio.h>
4 
5int main(int argc, char *argv[])
6{
7    int fd, copyfd;
8 
9    fd = open("./log.txt", O_RDWR);
10    //复制fd
11    copyfd = dup(fd);
12 
13    //输出copyfd,应当为4
14    printf("%d\n", copyfd);
15 
16    //打印出fd和copyfd的偏移量,都为0
17    printf("%d\n", (int)lseek(fd, 0, SEEK_CUR));
18    printf("%d\n", (int)lseek(copyfd, 0, SEEK_CUR));
19 
20    //将fd的偏移量+3
21    lseek(fd, 3, SEEK_SET);
22 
23    //打印出fd和copyfd的偏移量,都为3
24    printf("%d\n", (int)lseek(fd, 0, SEEK_CUR));
25    printf("%d\n", (int)lseek(copyfd, 0, SEEK_CUR));
26 
27    return 0;
28}

编译执行上例代码可以发现当移动fd的偏移量时,copyfd的偏移量也发生了变化。往文件里写入内容试试,先把log.txt内容清空。

1#include <unistd.h>
2#include <fcntl.h>
3#include <stdio.h>
4 
5int main(int argc, char *argv[])
6{
7    int fd, copyfd;
8 
9    fd = open("./log.txt", O_RDWR);
10    //复制fd
11    copyfd = dup(fd);
12 
13    char buf1[] = "hello ";
14    char buf2[] = "world!";
15 
16    //往fd文件写入内容
17    if (write(fd, buf1, 6) != 6) {
18        printf("write error!");
19    }
20 
21    //打印出fd和copyfd的偏移量,经过上面的写操作,都变成6了
22    printf("%d\n", (int)lseek(fd, 0, SEEK_CUR));
23    printf("%d\n", (int)lseek(copyfd, 0, SEEK_CUR));
24 
25    //往copyfd写入内容
26    if (write(copyfd, buf2, 6) != 6) {
27        printf("write error!");
28    }
29 
30    return 0;
31}

编译执行程序,log.txt的就有hello world!字符串了。

dup2函数

dup2(现存的文件描述符,可用的文件描述符)

dup2和dup函数一样,只是返回的文件描述符可以通过第二个参数”可用的文件描述符“指定。如果“可用的文件描述符“是打开状态,则会被关闭;如果”现存的文件描述符“和”可用的文件描述符“一样,则不会关闭,笔者认为这两个参数值一样的话,代码是没有任何意义的。

1#include <unistd.h>
2#include <fcntl.h>
3#include <stdio.h>
4 
5int main(int argc, char *argv[])
6{
7    int fd, copyfd;
8 
9    fd = open("./log.txt", O_RDWR);
10    //指定文件描述符号为1000
11    copyfd = dup2(fd, 1000);
12    //打印fd和copyfd,应当输出3 1000
13    printf("%d %d\n", fd, copyfd);
14 
15    return 0;
16}

上面程序就是指定返回的文件描述为1000,再来看下指定的文件描述符是打开的情况,修改上例代码,将文件描述符指定为1:

1copyfd = dup2(fd, 1);

编译执行程序将看不到任何输出,因为1是终端标准输出的标识符,经过这样一复制后,标准输出就被关闭了,使用printf自然看不到输出信息了。

参考资料:unix环境高级编程第二版,以上两图均来自这本书。

下面这段是CSDN下载的:

上面好像吵得很凶,下面的描述对于所有POSIX兼容的操作系统和ANSI兼容的C编译器都正确,别吵了,:) 


open和close是操作系统开放的两个系统调用接口,也就是说,进行这两个调用后,进程会进入内核态,由内核完成文件系统的操作任务。 
要注意的是,所有的系统调用其实都是二进制接口,而不是C语言接口,相关的转换工作由编译器完成(也有的是用库来封装,但是不推荐),也就是说,编译器会识别系统调用,将其封装为二进制的编码。 
这两个系统调用都是posix标准规定的,并且是“无缓冲”文件IO,也就是说,文件读写直接针对文件系统(注意,我可没有说“直接针对物理文件”,因为文件系统往往是有自己的缓冲的)。 
还有,这两个函数要求参数是“文件描述符”,也就是一个非负整数,类型是int,这一点   guyanhun(天涯-明月   C++从头开始)   说的很正确。 


至于fopen和fclose是ANSI   C的标准,注意拉,上面那个是操作系统标准,这个是编程语言的标准,也就是说,c的这两个函数“应该”能够在所有操作系统和文件系统上运行,而不依赖于文件系统的物理介质。 
这样的话,c就需要对文件操作建立一个单独的缓冲,因为可能有些文件系统就是需要这个东西(比如网络文件系统,必须将要写的数据打包,然后才能发出去,在我们常见的文件系统上其实不是必须的)。 
刚好,既然缓冲有不少好处(例如,可以不用关心每次读写多大的数据块),因此就被充分的用起来啦,不过其实C语言标准也没有说“必须要缓冲”,所以写代码的时候你也不用做这个假设。 
fopen和fclose会给每个打开的文件建立文件描述结构,也就是我们见到的FILE结构。这一点与上面的两个不同,因为使用整数做文件描述符是POSIX标准规定的。万一碰上其他标准的操作系统咋办?所以C语言就建立了自己文件描述结构。 

以下一段来自水木清华论坛:http://www.newsmth.net/nForum/#!article/LinuxDev/9513

fd = open(...); 
  
fp = fdopen(fd,"r"); 
  
.... 
  
这里需要fclose(fp)吗? 
  
close(fd); 


these operations are equal, i think the only different aspect is buffer. 


不是吧,以前看一本数说不应该fclose,否则会关闭open打开的fd 
【 在 Soaris (Ok Computer) 的大作中提到: 】 
: 如果不fclose会有内存泄漏 


不fclose是不行的,一是FILE里的buffered output没有flush, 
二是FILE本身可能分配了buffer,需要用fclose释放。 
当然,调用了fclose就不用close了。 

open是glibc封装的系统调用 
fopen是stdio提供的接口,封装了open 

初级,可能翻译成基础比较好,基本是系统调用。 
而FILE是buffer了的,读写速度快点。 
称作是标准I/O我想是因为它标准c要求的吧。 

View this article only 
网上论坛:comp.unix.programmer 
日期:1992-07-20 19:59:12 PST 
  
volpe@bart.NoSubdomain.NoDomain (Christopher R Volpe) writes: 
>Folks- 
>  I'm looking for advice on the following scenario: 

>  My routine is handed a file descriptor opened and managed by some 
> calling routine. (The descriptor actually corresponds to a connected 
> internet-domain socket, but hopefully that will not be relevant.) In my 
> routine, I would like to do some stdio-type operations (e.g. fprintf) on 
> the descriptor, so I open a stream with `stream=fdopen(fd,"w");'. Now, 
> before I return to the caller, I want to free up the stream since I no 
> longer need it, and since this routine may be called may times with 
> the same descriptor or different descriptors, and I don't want to 
> run out of streams after doing too many fdopens. However, I don't want 
> to disturb the underlying descriptor, so I can't do a "fclose(stream)". 
> Is there some way to "undo" an fdopen call? Something like an "fdclose" 
> call that might close the stream without closing the descriptor? 
You could dup() the file descriptor at the beginning of your routine, 
then associate this duplicated fd with a stream via fdopen(). At the 
end of your routine you can fclose() this stream without closing the 
socket connection because the original fd still holds it open. So 
after this you can continue to write to the socket from elsewhere 
in your program via the original fd that was before passed to your 
routine. 
Hope that helps. 
       Uwe 

原文:http://bytes.com/topic/c/answers/811133-temporarily-close-stdout

Joakim Hove
Thanks a lot:
The dup() approach with /dev/fd would be something like (not tested):

* * char buf[20];
* * int saved_stdout = dup(1);

* * freopen("/dev/null", "w", stdout);
* * lsb_submit( &request , &reply );
* * sprintf(buf, "/dev/fd/%d", saved_stdout);
* * freopen(buf, "w", stdout);
Worked like charm :-)

Joakim