博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
文件共享及dup函数
阅读量:6357 次
发布时间:2019-06-23

本文共 4407 字,大约阅读时间需要 14 分钟。

共享文件

UNIX系统支持在不同进程间共享打开的文件。在介绍dup()函数前,先介绍共享文件。
1.每个进程在进程表中都有一个记录项,每个记录项中有一张打开文件描述符表,可将视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:
  (a) 文件描述符标志。
  (b) 指向一个文件表项的指针。
2.内核为所有打开文件维持一张文件表。每个文件表项包含:
  (a) 文件状态标志(读、写、增写、同步、非阻塞等)。
  (b) 当前文件位移量。
  (c) 指向该文件v节点表项的指针。
3.每个打开文件(或设备)都有一个 v节点结构。v节点包含了文件类型和对此文件进行各种操作的函数的指针信息。对于大多数文件, v节点还包含了该文件的 i节点(索引节点) 。这些信息是在打开文件时从盘上读入内存的,所以所有关于文件的信息都是快速可供使用的。例如, i节点包含了文件的所有者、文件长度、文件所在的设备、指向文件在盘上所使用的实际数据块的指针等等

Kernel data structures for open files

如果两个独立的进程打开同一个文件,则如下图所示:

单个进程内的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所对应的文件中。下面是一个例子程序:

1     #define TESTSTR "Hello dup2\n"  2     int main() {
3 int fd3; 4 fd3 = open("testdup2.dat", 0666); 5 if (fd < 0) {
6 printf("open error\n"); 7 exit(-1); 8 } 9 if (dup2(fd3, STDOUT_FILENO) < 0) {
10 printf("err in dup2\n"); 11 } 12 printf(TESTSTR); 13 return 0; 14 }

其结果就是你在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(猜测:此时文件描述符表中并未增加s_fd表项),而在调用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);

1 #define TESTSTR "Hello dup2\n"  2 #define SIZEOFTESTSTR 11  3 int main() {
4 int fd3; 5 int s_fd; 6 int n_fd; 7 fd3 = open("testdup2.dat", 0666); 8 if (fd3 < 0) {
9 printf("open error\n"); 10 exit(-1); 11 } 12 /* 复制标准输出描述符 */ 13 s_fd = dup(STDOUT_FILENO); 14 if (s_fd < 0) {
15 printf("err in dup\n"); 16 } 17 /* 重定向标准输出到文件 */ 18 n_fd = dup2(fd3, STDOUT_FILENO); 19 if (n_fd < 0) {
20 printf("err in dup2\n"); 21 } 22 write(STDOUT_FILENO, TESTSTR, SIZEOFTESTSTR); /* 写入testdup2.dat中 */ 23 /* 重定向恢复标准输出 */ 24 if (dup2(s_fd, n_fd) < 0) {
25 printf("err in dup2\n"); 26 } 27 write(STDOUT_FILENO, TESTSTR, SIZEOFTESTSTR); /* 输出到屏幕上 */ 28 return 0; 29 }

注意这里我在输出数据的时候我是用了不带缓冲的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可以在父子进程之间建立一条“沟通的桥梁”。

 

转载地址:http://swbma.baihongyu.com/

你可能感兴趣的文章
ZOJ - 3703 Happy Programming Contest
查看>>
csa Round #66 (Div. 2 only)
查看>>
Get open Popups
查看>>
c#中两种不同的存储过程调用与比较
查看>>
设计模式-责任链模式(17)
查看>>
数论 + 公式 - HDU 4335 What is N?
查看>>
Public Prize
查看>>
生成 39 条形码
查看>>
抽象工厂理解
查看>>
计网第四章网络层(二)
查看>>
vs 行数
查看>>
nodejs下的express安装
查看>>
表单中的单文件点击和拖拽上传
查看>>
BZOJ1396 识别子串
查看>>
【转】numpy中 meshgrid 和 mgrid 的区别和使用
查看>>
【转】python中的闭包
查看>>
编程总结模版
查看>>
成为linux的合格公民
查看>>
小心陷阱:二维动态内存的不连续性
查看>>
转:关于启用 HTTPS 的一些经验分享(一)
查看>>