解惑dup/dup2(一)

本文解析了dup和dup2系统调用的功能及区别,并详细介绍了它们在CGI程序中的应用,包括如何重定向文件描述符及如何在子进程中恢复默认的STDOUT_FILENO。
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是程序运行所在的终端, 这个应该通过一种方法获得。实践证明这种方法是可行的,但是我总感觉有些不妥,不知道为什么,可能一些潜在的问题还没出现。目前我就想到这两种方法, 不知道你有什么好的想法? 有的话希望告诉我:)

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

运行了如下命令: ```bash ls -la /u01/app/oracle/oradata/dup/control01.ctl ``` 并得到了错误提示: ``` ls: cannot access /u01/app/oracle/oradata/dup/control01.ctl: No such file or directory ``` ### 解释: 这个错误表示系统无法找到指定路径下的文件 `/u01/app/oracle/oradata/dup/control01.ctl`。可能的原因包括: 1. **文件确实存在**:控制文件 `control01.ctl` 可能从未被创建,或已被删除。 2. **目录名错误**:`/u01/app/oracle/oradata/dup/` 这个路径可能拼写错误,或者数据库实例名为其他名称(如是 `dup` 而是 `orcl`、`prod` 等)。 3. **Oracle 实例未正确配置或未启动**:如果这是个复制(duplicate)数据库,可能 duplicate 操作未完成,导致控制文件未生成。 4. **权限问题**:当前用户没有读取该目录的权限,虽然通常会提示“Permission denied”,但某些情况下也可能表现为“No such file or directory”。 5. **符号链接损坏**:如果该路径是软链接,且指向了存在的位置,也会出现此错误。 --- ### 建议排查步骤(附命令): #### 1. 检查目标目录是否存在 ```bash ls -ld /u01/app/oracle/oradata/dup/ ``` - 如果返回“No such file or directory”,说明整个目录都存在。 - 如果权限足,尝试用 Oracle 用户登录后执行。 #### 2. 切换到 oracle 用户并重新检查 ```bash sudo su - oracle ls -la /u01/app/oracle/oradata/dup/ ``` > 大多数 Oracle 数据文件由 `oracle` 用户管理,普通用户可能看到。 #### 3. 查看实际的控制文件位置(从 Oracle 内部查询) 如果你可以连接到数据库,使用 SQL*Plus 查询控制文件位置: ```sql SQL> SELECT name FROM v$controlfile; ``` 这将列出所有正在使用的控制文件路径。例如输出可能是: ``` /u01/app/oracle/fast_recovery_area/dup/control02.ctl /u01/app/oracle/oradata/dup/control01.ctl ``` 如果这里显示路径存在但 `ls` 找到,则很可能是文件缺失或权限问题。 #### 4. 检查是否只是路径同(比如使用了 ASM 或 FRA) 有些环境使用 ASM 存储或快速恢复区(FRA),控制文件可能位于: ```bash /u01/app/oracle/fast_recovery_area/dup/ ``` 尝试: ```bash ls -la /u01/app/oracle/fast_recovery_area/dup/control* ``` #### 5. 验证数据库状态 ```bash ps -ef | grep pmon ``` 查看是否有名为 `dup` 的实例在运行。如果没有,那么控制文件自然需要存在。 --- ### 如何修复? #### 场景 A:你要重建控制文件 如果你正在做 Duplicate 数据库操作,而控制文件没生成,可能是 RMAN 报错中断了。你需要检查 RMAN 日志,并确保: - 目标目录已创建: ```bash mkdir -p /u01/app/oracle/oradata/dup chown -R oracle:oinstall /u01/app/oracle/oradata/dup ``` - 然后再运行 RMAN duplicate 命令。 #### 场景 B:手动创建控制文件(高级操作) 通过 `ALTER DATABASE CREATE CONTROLFILE;` 可以重建控制文件,但这属于灾难恢复操作,需谨慎。 --- ### 示例:创建缺失目录并赋权 ```bash sudo mkdir -p /u01/app/oracle/oradata/dup sudo chown -R oracle:oinstall /u01/app/oracle/oradata/dup sudo chmod 755 /u01/app/oracle/oradata/dup ``` 然后切换回 oracle 用户再试: ```bash su - oracle ls -la /u01/app/oracle/oradata/dup/control01.ctl ``` --- ### 总结 你的问题是“找到 control01.ctl”,根本原因可能是: - 文件路径错误 - 文件尚未生成 - 权限足 - 数据库未启动或配置错误 建议先确认数据库状态和真实控制文件路径。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值