《APUE》之文件系统篇

发表于二月 7, 2020星空下

主要关注两个方面:

  • 文件在文件系统和进程中的表现
  • 文件操作函数

文件操作函数

libc有fopen, fwrite, fread, fseek等库函数,系统调用有open, create, write, read, lseek, fcntl, ioctl等函数。

fwrite是带I/O缓冲的,为减少系统调用write次数以提升性能。系统调用write是带缓存的,使用内核高速缓存,为减少磁盘I/O次数,也是为提升性能。

但有些存储、消息队列系统,对I/O性能有更高要求,并且更了解自己的数据结构,能设计出更好的应用级缓存,Linux提供了直接I/O函数去掉内核缓存。

write函数成功返回,并不表示数据已经落盘成功,机器掉电仍有可能导致数据丢失。可以通过open文件时指定O_SYNC参数,或每次write完调用fsync函数,以fsync函数的返回值,作为写磁盘成功的依据,保证数据落盘。

文件表示形式

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

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

  • 文件描述符标志(close_on_exec)
  • 指向一个文件表项的指针

(2)内核为所有打开文件维持一张文件表。每个文件表项包含:

  • 文件状态标志(读、写、添加、同步和阻塞等)。
  • 当前文件偏移量。
  • 指向该文件v节点表项的指针。

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

Linux没有使用v节点,而是使用了通用i节点结构。虽然两种实现有所不同,但在概念上,v节点与i节点是一样的。两者都是指向文件系统特有的i节点结构。

一般的,若两个进程分别打开同一个文件,将拥有2个文件描述符,并有2个不同(2)文件表项,分别记录了自己的文件偏移量,也就是并发情况下,A进程可能覆盖B进程写的文件内容。

也有可能两个文件描述符,共享同一个文件表项,比如使用dup函数复制的文件描述符,或父进程创建文件描述符后,fork子进程,则子进程拥有文件描述符的副本,并且与父进程指向相同的文件表。

open文件时指定O_APPEND标志,则write会变成lseek(fd, 0, SEEK_END)+write的原子操作。这时由于dup或fork导致的打开文件描述符共享文件表,即共享了文件偏移。可实现交替写文件,互不冲突覆盖的能力。