观海听涛
Waiting for you

Linux下生成的文件能超过2G吗?

04 Apr 2014

刚才写了一段代码,会生成很大的一个文件,程序执行过程中提示:file too large。然后查看了一下生成文件的大小,刚好2G。我知道FAT32文件系统单个文件最大只支持4G,却不知为何linux的ext4文件系统却只支持最大2G的单个文件,好吧,我刚开始就是这样想的,当然,最后我知道是我错怪ext4了。

提出一个问题,一个文件系统支持的文件大小与什么有关?

对此鸟哥的私房菜中对此已做了比较深入的讲解:点我查看

以ext2文件系统来说的话,支持的单个文件大小与block的大小有关,Block 大小为1KB时,最大单一文件限制为16GB,最大文件系统总量为2TB,2KB时分别是256GB和8TB,4KB时分别为2TB和16TB。

感觉文件系统的逻辑结构还是很重要的。

ext2对单个文件至少都能支持16GB,那么ext4当然不可能小于16GB了。查了一下,现在ext3支持最大16TB的文件系统,单个文件最大2TB。Ext4最大支持1EB文件系统,和单个16TB的文件。至此,我明白2G的文件限制肯定不是文件系统造成的。

于是有一个核心问题需要解决,为什么不能生成大于2G的文件呢?

回想以前用过的lseek函数。所有打开的文件都有一个当前文件偏移量(current file offset),简称为 cfo。cfo 通常是一个非负整数,用于表明文件开始处到文件当前位置的字节数。读写操作通常开始于 cfo,并且使 cfo 增大,增量为读写的字节数。再看lseek的原型:off_t lseek(int handle, off_t offset, int fromwhere)。lseek所使用的cfo是off_t类型的,对于大多数系统,off_t类型默认是32位(可用sizeof(off_t)查看),也就是一个long型的,因此long型的数据只能表示正数到2^32-1,也就是2G。当写文件的时候,cfo达到了2^32-1(此时文件大小为2G),再往后就无法表示了,于是会返回file too large的错误。

最后一个问题,如何操作一个大于2G的文件?

从上面的分析我们知道,根本的原因在于off_t的表数范围,如何扩大它的表示范围是解决的思路。

lseek函数的原型可在 /usr/include/unistd.h 中查看(unistd.h只是使用extern对lseek函数进行声明,真正定义的位置在内核源码中):

extern __off_t lseek (int __fd, __off_t __offset, int __whence) __THROW

用户态使用的lseek接口是:

off_t lseek(int fd, off_t offset, int whence);

继续在/usr/include中查找off_t类型的定义:

#ifndef __USE_FILE_OFFSET64
typedef __off_t off_t;
#else
typedef __off64_t off_t;

从这段宏定义中,可以看到系统是通过__USE_FILE_OFFSET64来定义off_t到底属于哪一种类型的。__off64_t,从字面上就知道肯定是64位的,如果使用它的话,可以操作的文件最大将可以是(2^63-1)B。

继续搜索__USE_FILE_OFFSET64这个宏:在sys/types.h中有如下定义:

#if defined _FILE_OFFSET_BITS && _FILE_OFFSET_BITS == 64
# define __USE_FILE_OFFSET64    1
#endif

因此定义_FILE_OFFSET_BITS为64将会使__USE_FILE_OFFSET64宏被定义,进而引发off_t的数据类型为_off64_t,进而我们可以操作大于2G的文件。

于是解决方法就出来了,只需要要在编译时加上宏定义即可,比如:gcc -o program program.c -D_FILE_OFFSET_BITS=64。-D为宏定义,当然也可在应用程序中直接加入宏定义#define _FILE_OFFSET_BITS 64,不过必须放在#include unistd.h之前。