解惑dup/dup2(一)

2010-08-09 19:22

解惑dup/dup2(一)

by mutecat@byhh 2007-09.20

    最近一段时间在用c写cgi程序,接触了这两个系统调用dup/dup2,碰到了一些问题, 也解决了一些问题, 写出来与大家分享,也方便以后参考:)

1. 文件描述符在内核中数据结构

    在具体说dup/dup2之前, 我认为有必要先了解一下文件描述符在内核中的形态。一个进程在此存在期间,会有一些文件被打开,从而会返回一些文件描述符,从shell中运行一个进程,默认会有3个文件描述符存在(0、1、2), 0与进程的标准输入相关联,1与进程的标准输出相关联,2与进程的标准错误输出相关联,一个进程当前有哪些打开的文件描述符可以通过/proc/进程ID/fd目录查看。 下图可以清楚的说明问题:

  进程表项
————————————————

   fd标志 文件指针
      _____________________
fd 0:|________|____________|————> 文件表
fd 1:|________|____________|
fd 2:|________|____________|
fd 3:|________|____________|
     |     …….         |
     |_____________________|

                图1
       
文件表中包含:文件状态标志、当前文件偏移量、v节点指针,这些不是本文讨论的

重点,我们只需要知道每个打开的文件描述符(fd标志)在进程表中都有自己的文件表

项,由文件指针指向。

2. dup/dup2函数

APUE和man文档都用一句话简明的说出了这两个函数的作用:复制一个现存的文件描述符。

#include <unistd.h>

int dup(int oldfd);

int dup2(int oldfd, int newfd);

从图1来分析这个过程,当调用dup函数时,内核在进程中创建一个新的文件描述符,此描述符是当前可用文件描述符的最小数值,这个文件描述符指向oldfd所拥有的文件表项。

  进程表项
————————————————

   fd标志 文件指针
      _____________________
fd 0:|________|____________|                   ______
fd 1:|________|____________|—————-> |      |
fd 2:|________|____________|                  |文件表|
fd 3:|________|____________|—————-> |______|
     |     …….         |
     |_____________________|

                图2:调用dup后的示意图

如图2 所示,假如oldfd的值为1, 当前文件描述符的最小值为3, 那么新描述符3指向描述符1所拥有的文件表项。

dup2和dup的区别就是可以用newfd参数指定新描述符的数值,如果newfd已经打开,则先将其关闭。如果newfd等于oldfd,则dup2返回newfd, 而不关闭它。dup2函数返回的新文件描述符同样与参数oldfd共享同一文件表项。

Another way to duplicate a descriptor is with the fcntl function, which we describe in . Indeed, the call

dup(filedes);


is equivalent to

fcntl(filedes, F_DUPFD, 0);


Similarly, the call

dup2(filedes, filedes2);


is equivalent to

close(filedes2); fcntl(filedes, F_DUPFD, filedes2);


In this last case, the dup2 is not exactly the same as a close followed by an fcntl. The differences are as follows.

  1. dup2 is an atomic operation, whereas the alternate form involves two function calls. It is possible in the latter case to have a signal catcher called between the close and the fcntl that could modify the file descriptors. (We describe signals in .)

  2. There are some errno differences between dup2 and fcntl.

    The dup2 system call originated with Version 7 and propagated through the BSD releases. The fcntl method for duplicating file descriptors appeared with System III and continued with System V. SVR3.2 picked up the dup2 function, and 4.2BSD picked up the fcntl function and the F_DUPFD functionality. POSIX.1 requires both dup2 and the F_DUPFD feature of fcntl.

3. CGI中dup2

写过CGI程序的人都清楚,当浏览器使用post方法提交表单数据时,CGI读数据是从标准输入stdin, 写数据是写到标准输出stdout(c语言利用printf函数)。按照我们正常的理解,printf的输出应该在终端显示,原来CGI程序使用dup2函数将STDOUT_FINLENO(这个宏在unitstd.h定义,为1)这个文件描述符重定向到了连接套接字。

dup2(connfd, STDOUT_FILENO); /*实际情况还涉及到了管道,不是本文的重点*/

如第一节所说, 一个进程默认的文件描述符1(STDOUT_FILENO)是和标准输出stdout相关联的,对于内核而言,所有打开的文件都通过文件描述符引用,而内核并不知道流的存在(比如stdin、stdout),所以printf函数输出到stdout的数据最后都写到了文件描述符1里面。至于文件描述符0、1、2与标准输入、标准输出、标准错误输出相关联,这只是shell以及很多应用程序的惯例,而与内核无关。

用下面的流图可以说明问题:(ps: 虽然不是流图关系,但是还是有助于理解)

printf -> stdout -> STDOUT_FILENO(1) -> 终端(tty)

printf最后的输出到了终端设备,文件描述符1指向当前的终端可以这么理解:

STDOUT_FILENO = open("/dev/tty", O_RDWR);

使用dup2之后STDOUT_FILENO不再指向终端设备,而是指向connfd, 所以printf的输出最后写到了connfd。是不是很优美?:)

4. 如何在CGI程序的fork子进程中还原STDOUT_FILENO

如果你能看到这里,感谢你的耐心, 我知道很多人可能感觉有点复杂,其实复杂的问题就是一个个小问题的集合。所以弄清楚每个小问题就OK了,第三节中说道,STDOUT_FILENO被重定向到了connfd套接字, 有时候我们可能想在CGI程序中调用后台脚本执行,而这些脚本中难免会有一些输入输出, 我们知道fork之后,子进程继承了父进程的所有文件描述符,所以这些脚本的输入输出并不会如我们愿输出到终端设备,而是和connfd想关联了,这个显然会扰乱网页的输出。那么如何恢复STDOUT_FILENO和终端关联呢?

方法1:在dup2之前保存原有的文件描述符,然后恢复。

代码实现如下:

savefd = dup(STDOUT_FILENO); /*savefd此时指向终端*/

dup2(connfd, STDOUT_FILENO);   /*STDOUT_FILENO(1) 被重新指向connfd*/

….. /*处理一些事情*/

dup2(savefd, STDOUT_FILENO); /*STDOUT_FILENO(1) 恢复指向savefd*/

很遗憾CGI程序无法使用这种方法, 因为dup2这些不是在CGI程序中完成的,而是在web server中实现的,修改web server并不是个好主意。

方法2: 追本溯源,打开当前终端恢复STDOUT_FILENO。

分析第三节的流图, STDOUT_FILENO是如何和终端关联的? 我们重头做一遍不就行了, 代码实现如下:

ttyfd = open("/dev/tty", O_RDWR);

dup2(ttyfd, STDOUT_FILENO);

close(ttyfd);

/dev/tty是程序运行所在的终端, 这个应该通过一种方法获得。实践证明这种方法是可行的,但是我总感觉有些不妥,不知道为什么,可能一些潜在的问题还没出现。目前我就想到这两种方法, 不知道你有什么好的想法? 有的话希望告诉我:)

终于收尾了,一早上过来写,没想到写了两个小时才写完,好久没有写原创了,又重拾了以前那美妙的感觉:)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值