好吧,我知道这样的问题之前已经有各种forms的问题,我已经阅读了所有的问题,并尝试了所有build议,但我仍然无法在使用malloc,open,lseek,blah的64位系统上创build超过2GB的文件在太阳下肆无忌惮的一举一动
显然我在这里写c。 我正在运行Fedora 20,实际上我正在试图对文件进行mmap映射,但这不是失败的地方,我的原始方法是使用open(),然后lseek到文件应该结束的位置,在这种情况下3GB,编辑:然后在文件结尾位置写一个字节,实际创build这个大小的文件,然后mmap文件。 我不能超过2GB。 我也不能超过2GB的malloc。 ulimit -a等都显示无限,/etc/security/limits.conf显示没有,….
当我尝试超过2GB的时候,我得到了EINVAL errno,而lseek的ret值是-1.edit:lseek的size参数的types是off_t,它被定义为long int(64bit signed),而不是size_t,正如我所说的先前。
编辑:我已经试过定义_LARGEFILE64_SOURCE&_FILE_OFFSET_BITS 64并没有区别。 我也正在编译64位即-m64
我迷路了。 我不知道为什么我不能这样做。
任何帮助将不胜感激。
谢谢。
编辑:我已经删除了很多完全不正确的bab on声和我以后处理过的一些其他不重要的喧闹声。
我的2GB的问题是在多种不同types的可怕的交换。 混合签名和未签名是问题。 本质上,我传递给lseek的3GB位置被解释/转换为-1GB的位置,显然lseek并不是这样。 所以我的坏。 完全愚蠢。
我将改为使用posix_fallocate()作为p_lbuild议。 虽然它删除了一个函数调用,即只需要posix_fallocate而不是一个lseek,然后写一个,对我来说,这是不重要的,posix_fallocate正在做我正要做的直接lseek方法。 因此,特别要感谢p_l,并特别感谢NominalAnimal,他间接地了解了我的持久性,这让我意识到我无法计数,从而导致我接受posix_fallocate会起作用,因此转而使用它。
不pipe我使用的最终方法如何。 2GB的问题完全是我自己的废话编码,再次感谢EOF,chux,p_l和Jonathon Leffler,他们都提供了信息和build议,导致我为自己创build的问题。
我在一个答案中包含了这个简短的版本。
我的2GB的问题是在多种不同类型的可怕的交换。 混合签名和未签名是问题。 本质上,我传递给lseek的3GB位置被解释/转换为-1GB的位置,显然lseek并不是这样。 所以我的坏。 完全愚蠢的废话编码。
再次感谢EOF,chux,p_l和Jonathon Leffler,他们都提供了信息和建议,使我解决了我创建的问题和解决方案。
再次感谢p_l建议posix_fallocate(),并特别感谢NominalAnimal,他间接知道的持久性导致我意识到我无法计数,从而导致我接受posix_fallocate可以工作,因此转而使用它。
@p_l虽然我的实际问题的解决方案不是在你的答案,我仍然会投票你的答案,建议使用posix_fallocate,但我没有足够的积分来做到这一点。
首先,尝试:
//Before any includes: #define _LARGEFILE64_SOURCE #define _FILE_OFFSET_BITS 64
如果不行,把lseek
lseek64
就可以了
lseek64(fd, 3221225472, SEEK_SET);
比lseek
更好的选择可能是posix_fallocate()
:
posix_fallocate(fd, 0, 3221225472);
在调用mmap()之前;
我建议保持定义,虽然:)
这是我创建的测试程序( a2b.c
):
#include <assert.h> #include <errno.h> #include <fcntl.h> #include <inttypes.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> static void err_exit(const char *fmt, ...); int main(void) { char const filename[] = "big.file"; int fd = open(filename, O_RDONLY); if (fd < 0) err_exit("Failed to open file %s for reading", filename); struct stat sb; fstat(fd, &sb); uint64_t size = sb.st_size; printf("File: %s; size %" PRIu64 "\n", filename, size); assert(size > UINT64_C(3) * 1024 * 1024 * 1024); off_t offset = UINT64_C(3) * 1024 * 1024 * 1024; if (lseek(fd, offset, SEEK_SET) < 0) err_exit("lseek failed"); close(fd); _Static_assert(sizeof(size_t) > 4, "sizeof(size_t) is too small"); size = UINT64_C(3) * 1024 * 1024 * 1024; void *space = malloc(size); if (space == 0) err_exit("failed to malloc %zu bytes", size); *((char *)space + size - 1) = '\xFF'; printf("All OK\n"); return 0; } static void err_exit(const char *fmt, ...) { int errnum = errno; va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); if (errnum != 0) fprintf(stderr, ": (%d) %s", errnum, strerror(errnum)); putc('\n', stderr); exit(1); }
在Mac(Mac OS X 10.9.2 Mavericks,GCC 4.8.2,16 GiB物理RAM)上编译并运行时,使用命令行:
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \ -Wold-style-definition -Werror a2b.c -o a2b
并创建big.file
与:
dd if=/dev/zero of=big.file bs=1048576 count=5000
我得到了令人放心的结果:
File: big.file; size 5242880000 All OK
我必须使用_Static_assert
而不是static_assert
因为Mac <assert.h>
头文件没有定义static_assert
。 当我用-m32
编译时,触发了静态断言。
当我使用1 GiB虚拟物理内存的Ubuntu 13.10 64位虚拟机上运行它时(或者是重复性的?),我并不惊讶地得到输出结果:
File: big.file; size 5242880000 failed to malloc 3221225472 bytes: (12) Cannot allocate memory
我使用完全相同的命令行来编译代码; 它使用static_assert
代替_Static_assert
在Linux上进行_Static_assert
。 ulimit -a
的输出表明最大内存大小是无限的,但是这意味着“没有比机器上的虚拟内存量更小的限制”而不是更大的内存。
请注意,我的编译没有明确包含-m64
但它们是自动编译的64位。
你得到了什么? dd
可以创建大文件吗? 代码是否编译? (如果你的编译器没有C11的支持,那么你需要用一个正常的'动态'断言替换静态断言,删除错误信息。)代码是否运行? 你得到什么结果?
这是一个示例程序example.c
:
/* Not required on 64-bit architectures; recommended anyway. */ #define _FILE_OFFSET_BITS 64 /* Tell the compiler we do need POSIX.1-2001 features. */ #define _POSIX_C_SOURCE 200112L /* Needed to get MAP_NORESERVE. */ #define _GNU_SOURCE #include <unistd.h> #include <sys/mman.h> #include <fcntl.h> #include <errno.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #ifndef FILE_NAME #define FILE_NAME "data.map" #endif #ifndef FILE_SIZE #define FILE_SIZE 3221225472UL #endif int main(void) { const size_t size = FILE_SIZE; const char *const file = FILE_NAME; size_t page; unsigned char *data; int descriptor; int result; /* First, obtain the normal page size. */ page = (size_t)sysconf(_SC_PAGESIZE); if (page < 1) { fprintf(stderr, "BUG: sysconf(_SC_PAGESIZE) returned an invalid value!\n"); return EXIT_FAILURE; } /* Verify the map size is a multiple of page size. */ if (size % page) { fprintf(stderr, "Map size (%lu) is not a multiple of page size (%lu)!\n", (unsigned long)size, (unsigned long)page); return EXIT_FAILURE; } /* Create backing file. */ do { descriptor = open(file, O_RDWR | O_CREAT | O_EXCL, 0600); } while (descriptor == -1 && errno == EINTR); if (descriptor == -1) { fprintf(stderr, "Cannot create backing file '%s': %s.\n", file, strerror(errno)); return EXIT_FAILURE; } #ifdef FILE_ALLOCATE /* Allocate disk space for backing file. */ do { result = posix_fallocate(descriptor, (off_t)0, (off_t)size); } while (result == -1 && errno == EINTR); if (result == -1) { fprintf(stderr, "Cannot resize and allocate %lu bytes for backing file '%s': %s.\n", (unsigned long)size, file, strerror(errno)); unlink(file); return EXIT_FAILURE; } #else /* Backing file is sparse; disk space is not allocated. */ do { result = ftruncate(descriptor, (off_t)size); } while (result == -1 && errno == EINTR); if (result == -1) { fprintf(stderr, "Cannot resize backing file '%s' to %lu bytes: %s.\n", file, (unsigned long)size, strerror(errno)); unlink(file); return EXIT_FAILURE; } #endif /* Map the file. * If MAP_NORESERVE is not used, then the mapping size is limited * to the amount of available RAM and swap combined in Linux. * MAP_NORESERVE means that no swap is allocated for the mapping; * the file itself acts as the backing store. That's why MAP_SHARED * is also used. */ do { data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, descriptor, (off_t)0); } while ((void *)data == MAP_FAILED && errno == EINTR); if ((void *)data == MAP_FAILED) { fprintf(stderr, "Cannot map file '%s': %s.\n", file, strerror(errno)); unlink(file); return EXIT_FAILURE; } /* Notify of success. */ fprintf(stdout, "Mapped %lu bytes of file '%s'.\n", (unsigned long)size, file); fflush(stdout); #if defined(FILE_FILL) memset(data, ~0UL, size); #elif defined(FILE_ZERO) memset(data, 0, size); #elif defined(FILE_MIDDLE) data[size/2] = 1; /* One byte in the middle set to one. */ #else /* * Do something with the mapping, data[0] .. data[size-1] */ #endif /* Unmap. */ do { result = munmap(data, size); } while (result == -1 && errno == EINTR); if (result == -1) fprintf(stderr, "munmap(): %s.\n", strerror(errno)); /* Close the backing file. */ result = close(descriptor); if (result) fprintf(stderr, "close(): %s.\n", strerror(errno)); #ifndef FILE_KEEP /* Remove the backing file. */ result = unlink(file); if (result) fprintf(stderr, "unlink(): %s.\n", strerror(errno)); #endif /* We keep the file. */ fprintf(stdout, "Done.\n"); fflush(stdout); return EXIT_SUCCESS; }
编译并运行,使用例如
gcc -W -Wall -O3 -DFILE_KEEP -DFILE_MIDDLE example.c -o example ./example
以上将创建一个三千兆字节(1024 3 )稀疏文件data.map
,并将其中的中间字节设置为1
( \x01
)。 文件中的所有其他字节保持为零。 你可以运行
du -h data.map
看看这样一个稀疏的文件实际上在磁盘上,和
hexdump -C data.map
如果你想验证文件内容是我声称他们是。
有几个编译时标志(宏)可以用来改变示例程序的行为:
'-DFILE_NAME="filename"'
使用文件名称filename
而不是data.map
。 请注意,整个值是在单引号内定义的,这样shell就不会分析双引号。 (双引号是宏值的一部分。)
'-DFILE_SIZE=(1024*1024*1024)'
使用1024 3 = 1073741824字节映射,而不是默认的3221225472.如果表达式包含特殊字符shell将尝试评估,最好将其全部用单引号或双引号括起来。
-DFILE_ALLOCATE
分配整个映射的实际磁盘空间。 默认情况下,使用稀疏文件。
-DFILE_FILL
用(unsigned char)(~0UL)
填充整个映射,通常为255。
-DFILE_ZERO
将整个映射清零。
-DFILE_MIDDLE
将映射中的中间字节设置为1.所有其他字节保持不变。
-DFILE_KEEP
不要删除数据文件。 这对于探索映射在磁盘上实际需要多少数据很有用; 使用例如du -h data.map
。
在Linux中使用内存映射文件时需要考虑三个关键限制:
文件大小限制
较旧的文件系统(如FAT(MS-DOS))不支持大文件或稀疏文件。 如果数据集稀疏(包含大的空洞),则稀疏文件很有用; 在这种情况下,未设置的部分不存储在磁盘上,只读为零。
由于许多文件系统在读取和写入方面存在大于2 31 -1字节(2147483647字节)的问题,因此当前Linux内核在内部将每个操作限制为2 31 -1字节。 读取或写入调用不会失败,它只是返回一个短计数 。 我不知道任何类似的限制llseek()
系统调用的文件系统,但由于C库负责将lseek()/lseek64()
函数映射到正确的系统调用,所以很可能C库(而不是内核)限制了功能。 (在GNU C库和嵌入式GNU C库的情况下,这种系统调用映射依赖于编译时间标志,例如man 7 feature_test_macros
, man 2 lseek
和man 3 lseek64
。
最后,在大多数Linux内核中,文件位置处理不是原子的。 (修补程序在上游,但我不确定哪些版本包含它们。)这意味着如果多个线程使用相同的描述符以修改文件位置的方式,文件位置可能会被完全混淆。
内存限制
默认情况下,文件支持的内存映射仍受限于可用的内存和交换限制。 也就是说,默认的mmap()
行为是假定在内存压力下,脏页面被交换,而不是刷新到磁盘。 您需要使用特定于Linux的MAP_NORESERVE
标志来避免这些限制。
地址空间限制
在32位Linux系统上,用户空间进程可用的地址空间通常小于4 GiB; 这是一个内核编译时选项。
在64位Linux系统上,即使映射内容本身还没有出现故障,大型映射也会消耗大量内存。通常,每个单独页面在内存中需要8个字节的元数据( “页表条目” ),取决于架构。 使用4096字节的页面,这意味着最小开销为0.1953125%,并且设置例如TB级地图需要两个千兆字节的RAM在页表结构中!
Linux中的许多64位系统支持大量页面以避免这种开销。 在大多数情况下,由于配置和调整以及限制 ,大页面的使用受到限制 。 内核也可能会限制一个进程可以做一个巨大的页面映射; 一个健壮的应用程序将需要彻底的回退到正常的页面映射。
内核可能会对用户空间进程施加比资源可用性更严格的限制。 运行bash -c 'ulimit -a'
查看当前施加的限制。 (详细信息可以在man bash-builtins
的ulimit部分找到。)