共享文件

来源:互联网 发布:淘宝联盟推广软件 编辑:程序博客网 时间:2024/05/17 23:36

文件共享

内核通过打开三个数据结构来表示打开文件:

  • 描述符表(descriptor table)
    每个进程都有一张描述符表,它的表项是由文件描述符来索引的,每个打开的文件描述符表项指向文件表的一个表项。
  • 文件表(file table)
    打开的文件集合由一张文件表表示,所有进程共享这张表。每一个文件表项组成包括当前的文件位置,引用计数(reference count)(即当前指向该表项的文件描述符个数),以及一个指向v-node表中对应表项的指针等。关闭一个描述符,引用计数相应减1,直到引用计数为0时,内核才删除这个文件表项。
  • v-node表(v-node table)
    同文件表一样,所有的文件共享一张v-node表。每个表项包括stat结构中的大多数信息,包括st_mode和st_size。v-node表项唯一对应一个文件。

    不同描述符对应不同文件:
    没有共享
    注:文件表只有一张。

    不同描述符对应同一文件,但是对应不同文件表,所以对于不同描述符可以从文件不同位置读取数据:
    文件共享

父进程fork产生子进程,父子进程共享文件描述符(各自的描述符表),文件表和v-node表:
父子进程共享

I/O重定向

I/O重定向可以使用dup2来实现:

#include <unistd.h>int dup2(int oldfd, int newfd);                            //成功返回非负的描述符,失败返回-1

dup2复制描述符oldfd的表项到描述符newfd的表项,覆盖newfd表项以前的内容。如果newfd已经打开,dup2会先关闭newfd再复制oldfd的内容。例如,调用dup2(4,1),以前输入到fd1的内容,现在直接输入到fd4:
I/O重定向

调用dup2前:
fd1指向文件A,fd4指向文件B,两个文件表项的引用计数均为1.
调用dup2后:
文件A的引用计数减1,为0,被内核关闭,并删除;dup2复制fd4的描述符表项内容到fd1,文件B的引用计数相应加1。

标准I/O

标准I/O库是Unix I/O的较高级别代替。它把一个打开的文件模型化为一个流,用FILE类型指针指向它。每个ACSI C程序都有3个打开的流stdin, stdout, stderr,分别表示标准输入,标准输出,标准错误。
FILE类型的流是对文件描述符和流缓冲区的抽象。流缓冲区和RIO读缓冲区的目的是一样的,都是为了减少Linux I/O的调用次数。把要读的内容一次性读到缓冲区中,然后再按用户程序需求一点一点返回给用户程序。

建议:

  1. 尽量使用标准I/O。
  2. 不要用scanf和rio_readlineb来读取二进制文件。像scanf和rio_readlinb这样的函数是专门为文本文件设计的。
  3. 对套接字的I/O使用RIO函数。因为标准I/O会导致一些问题产生。标准I/O是全双工的。即输入,输出共用了同一个流,这就导致了输入输出有可能产生冲突:
    <1>跟在输出函数之后的输入函数。如果中间没有插入对fflush、fseek、fsetpos、rewind的调用,一个输入函数不能跟在一个输出函数之后。fflush 清空与流相关的缓冲区,其他三个都使用了unix I/O的lseek来重置当前文件的位置。
    <2>跟在输入函数之后的输出函数。如果中间没有插入fseek、fsetpos、rewind的调用,一个输出函数不能跟在一个输入函数之后,除非该输入函数遇到了一个EOF。
    这些限制给套接字的使用带来了一个问题,因为套接字使用lseek是非法的。对于<1>可以使用fflush解决,但是对于<2>唯一的方法就是对同一个套接字打开两个流,一个用来写,一个用来读:

    FILE *fpin, *fpout;fpin = fdopen(sockfd, "r"); fpout = fdopen(sockfd, "w");

    但是这会带来另一个问题,应用程序必须在两个流上都使用fclose,这样才能释放与流相关的内存资源,否则会有内存泄漏:

    fclose(fpin);fclose(fpout);

    这些操作中的每一个都是要关闭同一个套接字描述符,第二个就会失败。对于顺序执行的程序是没有没问题的,但是,对于线程化的程序关闭一个已经关闭的描述符会导致严重后果。
    因此,在套接字上使用RIO函数。如果需要格式化的输出,使用sprintf函数在内存中格式化一个字符串,然后用rio_writen把它发送给套接字接口。如果想格式化输入,使用rio_readlinb读取一个文本行,使用sscanf从文本行提取不同字段。

原创粉丝点击