UNIX环境高级编程学习笔记(三)文件共享和其他文件I/O函数

1.UNIX系统支持在不同的进程间共享打开文件。内核使用三种数据结构表示打开文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。

a. 每个进程在进程表中都有一个记录项,记录项中包含一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:

  • 文件描述符标识
  • 指向一个文件表项的指针

b. 内核为所有打开文件维持一张文件表,每个文件表项包括:

  • 文件状态标志(读、写、增写、同步、非阻塞等)
  • 当前文件位移量
  • 指向该文件v节点表项的指针

c. 每个打开文件(或设备)都有一个 v节点结构。

  • v节点包含了文件类型和对此文件进行各种操作的函数的指针信息对于大多数文件, v节点还包含了该文件的 i节点(索引节点) 。这些信息是在打开文件时从盘上读入内存的,所以所有关于文件的信息都是快速可供使用的。例如, i节点包含了文件的所有者、文件长度、文件所在的设备、指向文件在盘上所使用的实际数据块的指针等。

下图显示了进程的三张表之间的关系。该进程有两个不同的打开文件:一个文件打开为标准输入(文件描述符0) ,另一个打开为标准输出(文件描述符为1)。

这里写图片描述

如果两个独立进程各自打开了同一文件,如下图所示。

这里写图片描述

第一个进程使该文件在文件描述符 3上打开,而另一个进程则使此文件在文件描述符 4上打开。打开此文件的每个进程都得到一个文件表项,但对一个给定的文件只有一个 v节点表项。每个进程都有自己的文件表项的一个理由是:这种安排使每个进程都有它自己的对该文件的当前位移量。

给出了这些数据结构后,现在对前面所述的操作作进一步说明。
• 在完成每个w r i t e后,在文件表项中的当前文件位移量即增加所写的字节数。如果这使当前文件位移量超过了当前文件长度,则在 i节点表项中的当前文件长度被设置为当前文件位移量(也就是该文件加长了) 。
• 如果用O_APPEND标志打开了一个文件,则相应标志也被设置到文件表项的文件状态标志中。每次对这种具有添写标志的文件执行写操作时,在文件表项中的当前文件位移量首先被设置为i节点表项中的文件长度。这就使得每次写的数据都添加到文件的当前尾端处。
• lseek函数只修改文件表项中的当前文件位移量,没有进行任何I/O操作。
• 若一个文件用lseek被定位到文件当前的尾端,则文件表项中的当前文件位移量被设置为 i节点表项中的当前文件长度。可能有多个文件描述符项指向同一文件表项。在讨论dup函数时,我们就能看到这一点。在fork后也发生同样的情况,此时父、子进程对于每一个打开的文件描述符共享同一个文件表项。

注意,文件描述符标志和文件状态标志在作用范围方面的区别,前者只用于一个进程的一个描述符,而后者则适用于指向该给定文件表项的任何进程中的所有描述符。
上述的一切对于多个进程读同一文件都能正确工作。每个进程都有它自己的文件表项,其中也有它自己的当前文件位移量。但是,当多个进程写同一文件时,则可能产生预期不到的结果。为了说明如何避免这种情况,需要理解原子操作的概念。

2.原子操作
原子操作指的是由多步组成的一个操作。若该操作原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行全部步骤的一个子集。

假定有两个独立的进程A和B,都对同一文件进行添加操作。每个进程都已打开了该文件,但未使用O_APPEND标志。此时各数据结构之间的关系如上图中一样。每个进程都有它自己的文件表项,但是共享一个v节点表项。

if (lseek(fd, 0L, 2) < 0) /*position to EOF*/
    err_sys("lseek error");
if (write(fd, buff, 100) != 100) /*and write*/
    err_sys("write error");

假定进程A调用了lseek,它将对于进程A的该文件的当前位移量设置为1500字节(当前文件尾端处)。然后内核切换进程使进程B运行。进程B执行lseek,也将其对该文件的当前位移量设置为1500字节(当前文件尾端处)。然后B调用write,它将B的该文件的当前文件位移量增至 1600。因为该文件的长度已经增加了,所以内核对 v节点中的当前文件长度更新为 1600。然后,内核又进行进程切换使进程 A恢复运行。当A调用write时,就从其当前文件位移量 (1500)处将数据写到文件中去。这样也就覆盖了进程 B刚写到该文件中的数据。

问题在于逻辑操作“定位档到文件尾端处,然后写”使用了两个分开的函数调用。

Single UNIX Specification 包括了XSI扩展,该扩展允许原子性地定位并执行I/O。pread和pwrite就是这种扩展。

ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
ssize_t pwrite(int fd, void *buf, size_t nbytes, off_t offset);

调用pread相当于调用lseek和read,但pread与这种顺序调用有所区别:

  • 调用pread时,无法中断其定位和读操作
  • 不更新当前文件偏移量

调用pwrite与顺序调用lseek和write有类似的区别。

3.函数dup和dup2

int dup(int  fd) ;
int dup2(int  fd, int fd2) ;

用于复制一个现有的文件描述符:由dup返回的新文件描述符一定是当前可用文件描述符中的最小数值。用dup2则可以用fd2参数指定新描述符的数值。如果fd2已经打开,则先将其关闭。如若fd等于fd2,则dup2返回fd2,而不关闭。

4.函数sync、fsync和fdatasync

传统的UNIX系统实现在内核中设有缓冲区高速缓存或页高速缓存,大多数磁盘I/O都通过缓冲区进行。当我们向文件中写数据时,内核通常将数据复制到缓冲区中,然后排入队列,晚些时候再写入磁盘。这种方式被称为“延迟写”。
通常,当内核需要重用缓冲区来存放其他磁盘块数据时,它会把所有延迟写数据块写入磁盘。为了保证磁盘上实际文件与缓冲区内容的一致性,UNIX提供了sync、fsync和fdatasync三个函数。

#include <unistd.h>
void sync(void);
int fsync(int fd);
int fdatasync(int fd);
  • sync函数只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束。通常称为update的系统守护进程会周期性地(一般每隔30秒)调用sync函数。这就保证了定期冲洗内核的块缓冲区。
  • fsync函数只对由文件描述符filedes指定的单一文件起作用,并且等待写磁盘操作结束,然后返回。fsync可用于数据库这样的应用程序,这种应用程序需要确保将修改过的块立即写到磁盘上。
  • fdatasync函数类似于fsync,但它只影响文件的数据部分。而除数据外,fsync还会同步更新文件的属性。

5.函数fcntl.h

int fcntl(int fd,int cmd,... /*intarg*/);

fcntl可以改变已经打开文件的属性。
第三个参数总是一个整数,与上面所示函数原型中的注释部分相对应。但是说明记录锁时,第三个参数则是指向一个结构的指针。
fcntl函数有五种功能:

  • 复制一个现存的描述符(cmd=F_DUPFD)
  • 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)
  • 获得/设置文件状态标志(cmd=F_GETFL或F_SETFL)
  • 获得/设置异步I/O有权(cmd=F_GETOWN或F_SETOWN)
  • 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW)

其中各cmd的含义是:

  • F_DUPFD 复制文件描述符fd,新文件描述符作为函数值返回。它是尚未打开的各描述符中大于或等于第三个参数值(取为整型值)中各值的最小值。
  • F_GETFD 对应于fd的文件描述符标志作为函数值返回。当前只定义了一个文件描述符标志FD_CLOEXEC。
  • F_SETFD 对于fd设置文件描述符标志。新标志值按第三个参数(取为整型值)设置。
  • F_GETFL 对应于fd的文件状态标志作为函数值返回。
  • F_SETFL 将文件状态标志设置为第三个参数的值(取为整型值)。
  • F_GETOWN 取当前接收SIGIO和SIGURG信号的进程I D或进程组I D。
  • F_SETOWN 设置接收SIGIO和SIGURG信号的进程I D或进程组I D。正的arg指定一个进程I D,负的arg表示等于arg绝对值的一个进程组I D。

6.函数ioctl
ioctl是I/O操作的杂物箱。不能用本章中其他函数表示的I / O操作通常都能用ioctl表示。终端I/O是ioctl的最大使用方面。

7.较新的系统都提供名为/dev/fd的目录,其目录项是名为0、1、2等的文件。打开文件/dev/fd/n等效于复制描述符n(假定描述符n是打开的)。

fd = open("/dev/fd/0", mode);

大多数系统忽略所指定的mode,而另外一些则要求mode是所涉及的文件(在这里则是标准输入)原先打开时所使用的mode的子集。因为上面的打开等效于:

fd = open("/dev/fd/0",O_RDWR);

我们仍然不能对fd进行写操作。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值