分类: 系统运维
2012-03-28 12:52:08
每个打开的文件都有一个相应的“当前文件偏移量(current file
offset)”,一般是一个表示从文件开头开始的字节数量的非负整数。(我们待会来讨论下“非负”的一个例外。)读和写的操作一般从当前文件偏移量开
始,而且会根据读出或写入的字节数导致偏移量的增加。当文件打开时,这个偏移量默认初始化为0,除非O_APPEND选项被指定。
一个打开的文件的偏移量可以通过调用lseek显示地设置:
#include
off_t lseek(int fileds, off_t offset, int whence);
成功返回新的文件偏移,错误返回-1。
offset的解释取决于whence参数:
如果whence为SEEK_SET,那文件偏移量会设置为从文件开头之后的offset字节数的位置;
如果whence为SEEK_CUR,那文件偏移量会设置为当前的偏移量加上offset的值。offset可以是正也可以是负的;
如果whence为SEEK_END,那文件偏移量为文件的尺寸加上offset的值。offset可以是正也可以是负的。
因为一个成功的lseek调用会返回新的文件偏移量,我们可以通过从当前位置seek 0字节来决定当前的偏移量:
off_t currpos;
durrpos = lseek(fd, 0, SEEK_CUR);
这个技术同样也可以用来确定一个文件是否有seek的能力。如果文件描述符指向一个管道、FIFO或套接字,lseek把errno设置为ESPIPE并返回1。
这三个符号常量SEEK_SET、SEEK_CUR、SEEK_END在System V中被引入。在此之前,whence被指定为0(绝对的)、1(相对于当前偏移量)或2(相对于文件结尾)。许多软件仍然使用这些数字的硬编码。
lseek的字符“l”意思是“long integer”。在引入off_t数据类型前,offset参数和返回值是长整型。lseek在版本7当长整型被加入到C时引入。(版本6通过函数seek和tell提供类似的功能。)
下面的代码用来测试标准输入是否可以seek:
通常情况下,一个文件的当前偏移量必须是非负整数。尽管如此,有些设备可能允许负的偏移量。但对于普通文件,偏移量必须是非负的。因为负数偏移量是可能的,我们应该小心比较lseek的返回值,应测试它是否等于-1,而不能测试它是否小于0。
在Intel x86处理器上的FreeBSD的/dev/kmem设备运行负的偏移量。
因为偏移量(off_t)是一个有符号的数据类型,我们在最大文件尺寸里丢失了一个2的因子。如果off_t是32位整型,文件最大尺寸为2^32-1字节。
lseek只在内核里记录当前文件偏移量--它没有引起任何的I/O操作。这个偏移量在下次读或写操作时被使用。
文件的偏移量可以比文件当前尺寸更大,在这种情况下下次文件的write会扩展这个文件。这表示会在文件里创建一个空洞,而这是允许的。在文件里读任何没有写过的字节都会得到0。
文件的空洞不需要在磁盘上战用存储空间。取决于文件系统实现,当你找到超过文件末尾的位置然后写时,新的磁盘块可能会被分配用来存储数据,但没有必要为旧的文件末尾与你开始写的位置之间的数据分配磁盘空间。
下面的代码在创建了一个带空洞的文件:
它的大小是16394字节。再后od -c file.hole命令来查看文件的内容:
0000000 a b c d e f g h i j \0 \0 \0 \0 \0 \0
0000020 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
0040000 A B C D E F G H I J
0040012
可以看到没有写过的字节都被读回0。每行开头的7位数字,是表示文件偏移量的八进制数。
为了证明这个文件确实有个空洞,把它和另一个相同尺寸,没有空洞的文件进行比较:
ls -ls file file.hole file.nohole
16 -rw-r--r-- 1 tommy tommy 16394 2012-02-17 12:26 file.hole
20 -rw-r--r-- 1 tommy tommy 16394 2012-02-17 12:28 file.nohole
可以看到无洞的文件使用了20个磁盘块,而有洞的文件使用了16个块。3.8节会讨论write函数,而4.12节会深入讨论带有空洞的文件。
因为lseek使用的偏移量地址由off_t表示,所以系统实现被允许支持任何适用于它们平台的尺寸。今天多数平台提供两个操作文件偏移量的接口集:一个集合使用32位文件偏移量而另一个使用64位文件偏移量。
单一UNIX规范通过sysconf函数为应用程序提供了一种知道哪种环境被支持的方法。下表总结了定义的sysconf常量:
数据大小选项与对应在sysconf的命名参数 | ||
选项名 | 描述 | 命名参数 |
_POSIX_V6_ILP32_OFF32 | int、long、指针和off_t类型是32位的。 | _SV_V6_ILP32_OFF32 |
_POSIX_V6_ILP32_OFFBIG | int、long和指针类型是32位的,off_t是至少64位的。 | _SV_V6_ILP32_OFFBIG |
_POSIX_V6_LP64_OFF64 | int类型是32位的,long、指针和off_t类型是64位的。 | _SC_V6_LP64_OFF64 |
_POSIX_V6_LP64_OFFBIG | int类型是32位的,long、指针和off_t类型是至少64位的。 | _SC_V6_LP64_OFFBIG |
c99编译器需要我们使用getconf命令来把需要的数据大小模型映射到编译和链接我们程序时所需的标志。根据每个平台所支持的环境,可能需要不同的标志和库。
不幸的是,这是系统实现没有跟上标准的一个领域。更令人疑惑的事情是在SUS第2版和第3版之间的名字改变。
为了解决这个问题,应用程序可以通过设置_FILE_OFFSET_BITS常量的值为64来启用64位偏移。这样会把off_t的定义改变为64位有符
号整型。把_FILE_OFFSET_BITS设置为32会启用32位偏移。尽管如此,要注意虽然本文讨论的四个平台都可以设置
_FILE_OFFSET_BITS来同时支持32位和64位的偏移量,但这并不保证可以移植。
注意尽管你可能启用了64位偏移,你是否能创建大于2TB(2^31-1字节)的文件仍取决于底下的文件系统类型。