我正在尝试使用以下代码(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;
}
使用gcc -std=c99 test.c -o test
和file
测试返回来编译 test.c: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和系统调用来完成。我的代码出了什么问题?
答案 0 :(得分:63)
MAP_PRIVATE
映射需要内存预留,因为写入这些页面可能会导致写时复制分配。这意味着你不能映射比物理ram + swap大得多的东西。请尝试使用MAP_SHARED
映射。这意味着对映射的写入将反映在磁盘上 - 因此,内核知道它总是可以通过写回来释放内存,所以它不会限制你。
我还注意到你正在用PROT_WRITE
进行映射,但是你继续读取内存映射。您还使用O_RDONLY
打开了文件 - 这本身可能是您的另一个问题;如果要将O_RDWR
与PROT_WRITE
一起使用,则必须指定MAP_SHARED
。
仅对于PROT_WRITE
,这恰好适用于x86,因为x86不支持只写映射,但可能导致其他平台上的段错误。请求PROT_READ|PROT_WRITE
- 或者,如果您只需阅读PROT_READ
。
在我的系统上(带有676MB RAM的VPS,256MB交换),我重现了你的问题;更改为MAP_SHARED
会导致EPERM
错误(因为我不允许写入使用O_RDONLY
打开的支持文件)。更改为PROT_READ
和MAP_SHARED
可以使映射成功。
如果您需要修改文件中的字节,一个选项是将您要写入的文件的范围设为私有。也就是说,munmap
并使用MAP_PRIVATE
重新映射您要写入的区域。当然,如果您打算写入整个文件,那么您需要8GB内存才能这样做。
或者,您可以将1
写入/proc/sys/vm/overcommit_memory
。这将允许映射请求成功;但是,请记住,如果你真的尝试使用完整的8GB COW内存,你的程序(或其他程序!)将被OOM杀手杀死。
答案 1 :(得分:4)
您没有足够的虚拟内存来处理该映射。
作为一个例子,我这里有一台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
因此,请确保您有足够的虚拟内存来保存该映射(即使您只触摸该文件中的几个页面)。
答案 2 :(得分:4)
Linux(显然还有一些其他UNIX系统)具有mmap(2)的MAP_NORESERVE
标志,可用于显式启用交换空间过量使用。当您希望映射的文件大于系统上可用的可用内存量时,这非常有用。
当与MAP_PRIVATE
一起使用时,这尤其方便,并且只打算写入内存映射范围的一小部分,因为否则会触发整个文件的交换空间预留(或导致系统返回{ {1}},如果尚未启用系统范围的过度使用并且您超出了系统的可用内存。)
需要注意的问题是,如果你写入大部分内存,延迟交换空间预留可能会导致应用程序占用所有空闲RAM并交换系统,最终触发OOM杀手( Linux)或导致您的应用获得ENOMEM
。