dup/dup2用法

原文出自哪里忘记了,主要内容来自<<advanced unix programming>>谁见到了纠正下。

这次专业一点:

 

函数名: dup2

功  能: 复制文件句柄

用  法: int dup2(int oldhandle, int newhandle);

 

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 -------------/

   ------------

调用 n_fd=dup2(fd3,STDOUT_FILENO);后状态为:

进程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);//新建一个文件testdup2.dat,权限666

        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);   /* 写入testdup2.dat中 */

        /* 重定向恢复标准输出 */

        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

   ------------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值