系统编程:文件空洞
在介绍文件空洞之前,先介绍一下改变文件偏移量的系统函数lseek()。在了解了文件偏移量之后,再介绍文件空洞产生的场景和系统实现。
lseek()
对于每个打开的文件,系统内核会记录其文件偏移量。文件偏移量是指执行下一个read()或者write()操作的文件起始位置,会以相对于文件头部起始点的文件当前位置来表示。文件第一个字节的偏移量为0。
#include <unistd.h>
lseek(int fd, off_t offset, int whence);
offset参数指定了一个以字节为单位的数值。whence参数则表明应参照哪个基点来解释offset参数,应为下列其中之一:
SEEK_SET
将文件偏移量设置为从文件头部起始点开始的offset个字节。
SEEK_CUR
相对于当前文件偏移量,将文件偏移量调整offset个字节。
SEEK_END
将文件偏移量设置为起始于文件尾部的offset个字节。也就是说,offset参数应该从文件最后一个字节之后的下一个字节算起。
如果whence参数值为SEEK_CUR或SEEK_END,offset参数可以为正数也可以为负数;如果whence参数值为SEEK_SET,offset参数值必须为非负数。
lseek(fd, 0, SEEK_SET); /* start of file */
lseek(fd, 0, SEEK_END); /* next byte after the end of the file */
lseek(fd, -1, SEEK_END); /* last byte of file */
lseek(fd, -10, SEEK_CUR); /* 10 bytes prior to current location */
lseek(fd, 10000, SEEK_END); /* 10000 bytes past last byte of file */
lseek()并不适用于所有类型的文件。不允许将lseek()应用于管道、FIFO、socket或者终端。
文件空洞
如果程序的文件偏移量依然跨越了文件末尾,然后再执行I/O操作,将会发生什么情况呢?read()调用将返回0,表示文件末尾。令人惊讶的是,write()函数可以在文件结尾后的任意位置写入数据。
从文件结尾后到新写入数据间的这段空间被称为文件空洞。从编程角度看,文件空洞中是存在字节的,读取空洞将返回0(空字节)填充的缓冲区。然而,文件空洞不占用任何磁盘空间,直到后续某个时间节点,在文件空洞中写入了数据,文件系统才会为之分配磁盘块。
文件空洞的主要优势在于,与为实际需要的空字节分配磁盘块相比,稀疏填充的文件会占用较少的磁盘空间。核心转储文件(core dump)是包含空洞文件的常见例子。
多数“原生”UNIX文件系统都支持文件空洞的概念,但很多“非原生”文件系统(比如微软的VFAT)并不支持这一概念。不支持文件空洞的文件系统会显式地将空字节写入文件。
空洞的存在意味着一个文件名义上的大小可能要比其占用的磁盘存储总量要大(有时会大出很多)。向文件空洞中写入字节,内核需要为其分配存储单元,即使文件大小不变,系统的可用磁盘空间也将减少。
参考
- Linux系统编程手册,[德]Michael Kerrisk