Mmap()整个大文件
我正在试图使用下面的代码(test.c)“mmap”一个二进制文件(〜8Gb)。
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define handle_error(msg) \ do { perror(msg); exit(EXIT_FAILURE); } while (0) int main(int argc, char *argv[]) { const char *memblock; int fd; struct stat sb; fd = open(argv[1], O_RDONLY); fstat(fd, &sb); printf("Size: %lu\n", (uint64_t)sb.st_size); memblock = mmap(NULL, sb.st_size, PROT_WRITE, MAP_PRIVATE, fd, 0); if (memblock == MAP_FAILED) handle_error("mmap"); for(uint64_t i = 0; i < 10; i++) { printf("[%lu]=%X ", i, memblock[i]); } printf("\n"); return 0; }
test.c使用gcc -std=c99 test.c -o test
编译gcc -std=c99 test.c -o test
并返回gcc -std=c99 test.c -o test
file
: test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, not stripped
虽然这对小文件工作正常,但是当我尝试加载大文件时,出现了分段错误。 该程序实际返回:
Size: 8274324021 mmap: Cannot allocate memory
我设法使用boost :: iostreams :: mapped_file来映射整个文件,但是我想用C和系统调用来完成。 我的代码有什么问题?
MAP_PRIVATE
映射需要内存预留,因为写入这些页面可能导致写入时复制分配。 这意味着你不能映射比你的物理内存+交换大得多的东西。 尝试使用MAP_SHARED
映射。 这意味着写入映射将会反映在磁盘上 – 因此,内核知道它总是可以通过回写来释放内存,所以它不会限制你。
我也注意到你正在使用PROT_WRITE
映射,但是你继续从内存映射中读取数据。 你也用O_RDONLY
打开这个文件 – 这本身对你来说可能是另一个问题; 如果要使用带MAP_SHARED
PROT_WRITE
,则必须指定O_RDWR
。
至于PROT_WRITE
,这只适用于x86,因为x86不支持只写映射,但可能会在其他平台上导致段错误。 请求PROT_READ|PROT_WRITE
– 或者,如果您只需要读取PROT_READ
。
在我的系统(VPS 676MB RAM,256MB交换),我转载了你的问题; 更改为MAP_SHARED
导致EPERM
错误(因为我不允许写入使用O_RDONLY
打开的支持文件)。 更改为PROT_READ
和MAP_SHARED
允许映射成功。
如果你需要修改文件中的字节,一个选项就是使私有只是你要写入的文件的范围。 也就是说, munmap
并用MAP_PRIVATE
重新映射您要写入的区域。 当然,如果你打算写入整个文件,那么你需要8GB的内存来完成。
或者,您可以写入1
到/proc/sys/vm/overcommit_memory
。 这将允许映射请求成功; 但是,请记住,如果你真的尝试使用8GB的COW内存,你的程序(或其他程序!)将被OOM杀手杀死。
您没有足够的虚拟内存来处理该映射。
举个例子,我在这里有一台8G的内存和8G的交换机(所以有16G的虚拟内存可用)。
如果我在〜8G的VirtualBox快照上运行你的代码,它工作正常:
$ ls -lh /media/vms/.../snap.vdi -rw------- 1 me users 9.2G Aug 6 16:02 /media/vms/.../snap.vdi $ ./a.out /media/vms/.../snap.vdi Size: 9820000256 [0]=3C [1]=3C [2]=3C [3]=20 [4]=4F [5]=72 [6]=61 [7]=63 [8]=6C [9]=65
现在,如果我放弃交换,我剩下8G的内存。 ( 不要在活动的服务器上运行这个。)结果是:
$ sudo swapoff -a $ ./a.out /media/vms/.../snap.vdi Size: 9820000256 mmap: Cannot allocate memory
因此,请确保您有足够的虚拟内存来保存该映射(即使您只触摸该文件中的几个页面)。
Linux(以及其他一些UNIX系统)具有mmap(2)的MAP_NORESERVE
标志,可用于明确启用交换空间过度使用。 当您希望映射的文件大于系统上可用的可用内存量时,这会很有用。
这与MAP_PRIVATE
使用时特别方便,只打算写入内存映射范围的一小部分,因为否则会触发整个文件的交换空间预留(或者导致系统返回ENOMEM
,如果系统范围的overcommitting hasn'没有被启用,你超过了系统的空闲内存)。
要注意的问题是如果你写这个内存的一大部分,懒惰的交换空间预留可能会导致你的应用程序消耗系统中所有可用的RAM和交换,最终触发OOM杀手(Linux)或导致您的应用程序收到一个SIGSEGV
。