Mmap()一个完整的大文件

时间:2011-08-28 16:25:59

标签: c 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;
}
使用gcc -std=c99 test.c -o testfile测试返回来编译

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和系统调用来完成。我的代码出了什么问题?

3 个答案:

答案 0 :(得分:63)

MAP_PRIVATE映射需要内存预留,因为写入这些页面可能会导致写时复制分配。这意味着你不能映射比物理ram + swap大得多的东西。请尝试使用MAP_SHARED映射。这意味着对映射的写入将反映在磁盘上 - 因此,内核知道它总是可以通过写回来释放内存,所以它不会限制你。

我还注意到你正在用PROT_WRITE进行映射,但是你继续读取内存映射。您还使用O_RDONLY打开了文件 - 这本身可能是您的另一个问题;如果要将O_RDWRPROT_WRITE一起使用,则必须指定MAP_SHARED

仅对于PROT_WRITE,这恰好适用于x86,因为x86不支持只写映射,但可能导致其他平台上的段错误。请求PROT_READ|PROT_WRITE - 或者,如果您只需阅读PROT_READ

在我的系统上(带有676MB RAM的VPS,256MB交换),我重现了你的问题;更改为MAP_SHARED会导致EPERM错误(因为我不允许写入使用O_RDONLY打开的支持文件)。更改为PROT_READMAP_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