UNIX环境高级编程 - 文件I/O - 读写/共享文件
函数lseek
使用 lseek
函数显式的为一个打开文件设置偏移量。
每个打开的文件都有一个与其关联的“当前文件偏移量”。它通常是个非负整数,用于度量从文件开始处计算的字节数。
读、写操作都从当前文件偏移量处开始,并使偏移量增加所读写的字节数。
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
- 参数:
fd
:打开的文件的文件描述符whence
:必须是SEEK_SET
、SEEK_CUR
、SEEK_END
三个常量之一offset
:- 若
whence
是SEEK_SET
,则将该文件的偏移量设置为距离文件开始处offset
个字节 - 若
whence
是SEEK_CUR
,则将该文件的偏移量设置为当前值加上offset
个字节,offset
可正,可负 - 若
whence
是SEEK_END
,则将该文件的偏移量设置为文件长度加上offset
个字节,offset
可正,可负
- 若
- 返回值:
- 成功: 返回新的文件偏移量
- 失败:返回 -1
一些关于 lseek
和文件偏移量的注意事项:
-
打开一个文件时,除非指定
O_APPEND
选项,否则系统默认将该偏移量设为0 -
如果文件描述符指定的是一个管道、FIFO、或者网络套接字,则无法设定当前文件偏移量,则
lseek
将返回 -1 ,并且将errno
设置为ESPIPE
。 -
对于普通文件,其当前文件偏移量必须是非负值。但是某些设备运行负的偏移量出现。因此比较
lseek
的结果时,不能根据它小于0 就认为出错。要根据是否等于 -1 来判断是否出错。 -
lseek
并不会引起任何 I/O 操作,lseek
仅仅将当前文件的偏移量记录在内核中。 -
当前文件偏移量可以大于文件的当前长度。此时对该文件的下一次写操作将加长该文件,并且在文件中构成一个空洞。空洞中的内容位于文件中但是没有被写过,其字节被读取时都被读为0
文件中的空洞并不要求在磁盘上占据存储区。具体处理方式与操作系统有关
函数read
调用 read
函数从打开文件中读取数据。
读操作从文件的当前偏移量开始,在成功返回之前,文件的当前偏移量会增加实际读到的字节数。
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);
- 参数:
fd
:打开的文件的文件描述符buf
:存放读取内容的缓冲区的地址(由程序员手动分配)nbytes
:期望读到的字节数
- 返回值:
- 成功:返回读到的字节数,若已到文件尾则返回 0
- 失败:返回 -1
有多种情况可能导致实际读到的字节数少于期望读到的字节数:
- 读普通文件时,在读到期望字节数之前到达了文件尾端
- 当从终端设备读时,通常一次最多读取一行(终端默认是行缓冲的)
- 当从网络读时,网络中的缓存机制可能造成返回值小于期望读到的字节数
- 当从管道或者
FIFO
读时,若管道包含的字节少于所需的数量,则read
只返回实际可用的字节数 - 当从某些面向记录的设备(如磁带)中读取时,一次最多返回一条记录
- 当一个信号造成中断,而已读了部分数据时。
函数write
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t nbytes);
- 参数:
fd
:打开的文件的文件描述符buf
:存放待写的数据内容的缓冲区的地址(由程序员手动分配)nbytes
:期望写入文件的字节数
- 返回值:
- 成功:返回已写的字节数
- 失败:返回 -1
write
的返回值通常都是与 nbytes
相同。否则表示出错。
write
出错的一个常见原因是磁盘写满,或者超过了一个给定进行的文件长度限制
对于普通文件,写操作从文件的当前偏移量处开始。如果打开文件时指定了 O_APPEND
选项,则每次写操作之前,都会将文件偏移量设置在文件的当前结尾处。在一次成功写之后,该文件偏移量增加实际写的字节数。
I/O效率
下图显示了用 20 种不同的缓冲区长度,读 516,581,760 字节的文件所得到的结果:
此测试所用的文件系统是 Linux ext4 文件系统,其磁盘块长度为 4096 字节(4K)。
- 系统 CPU 时间的几个最小值差不多出现在 BUFFSIZE 为 4096 及以后的位置,继续增加缓冲区长度对此时间几乎没有影响。
- 缓冲区长度小至 32 字节时的时钟时间与拥有较大缓冲区长度时的时钟时间几乎一样。
Why?
大多数文件系统为改善性能都采用某种预读(read ahead)技术。当检测到正进行顺序读取时,系统就试图读入比应用所要求的更多数据,并假想应用很快就会读这些数据。(利用了局部性原理)
文件共享
UNIX 系统支持在不同进程间共享打开文件。
内核使用三种数据结构描述打开文件。它们之间的关系决定了一个进程与另一个进程在打开的文件之间的相互影响。
-
内核为每个进程分配一个进程表项(所有进程表项构成进程表),进程表中都有一个打开的文件描述符表。每个文件描述符占用一项,其内容为:
- 文件描述符标志
- 指向一个文件表项的指针
-
内核为每个打开的文件分配一个文件表项(所有的文件表项构成文件表)。每个文件表项的内容包括:
- 文件状态标志(读、写、添写、同步和阻塞等)
- 当前文件偏移量
- 指向该文件 v 结点表项的指针
-
每个打开的文件或者设备都有一个 v 结点结构(v-node)。 v 结点结构的内容包括:
- 文件类型和对此文件进行各种操作函数的指针。
- 对于大多数文件, v 结点还包含了该文件的 i 结点。
这些信息都是在打开文件时从磁盘读入内存的。如 i 结点包含了文件的所有者、文件长度、指向文件实际数据在磁盘上所在位置的指针等等。 v 结点结构和 i 结点结构实际上代表了文件的实体。
Linux 没有将相关数据结分为 i 节点和 v 节点,而是采用了一个与文件系统相关的 i 节点和个与文件系统无关的 i 节点。
一个进程打开多个文件:
两个进程打开同一个文件:
可能有多个文件描述符指向同一个文件表项。(dup、fork)
dup的情况:
fork的情况: