【问题标题】:Mmap() an entire large filemmap() 整个大文件
【发布时间】:2011-11-05 12:50:44
【问题描述】:

我正在尝试使用以下代码 (test.c) “映射”一个二进制文件 (~ 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 testfile 编译,测试返回: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 和系统调用来映射。我的代码有什么问题?

【问题讨论】:

  • 您需要使用O_LARGEFILE 标志打开文件。检查手册。不确定 >4GB 文件是否可以mmaped。
  • 此处无法重现。您的代码在 9G 文件上运行良好。你有多少(RAM + SWAP)?您当前的 /proc/sys/vm/overcommit_memory 政策是什么?
  • @Mat $free -m #Mem{ 总计:1984,使用:1923,免费 60} Swap{ 总计:2021,使用:0,免费:2021} $ cat /proc/sys/vm /overcommit_memory #returns 0
  • @phoxis 32 位机器需要该标志。 Ref
  • @Emer:抱歉,我刚刚注意到x86_64

标签: c mmap


【解决方案1】:

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

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

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

在我的系统(具有 676MB RAM,256MB 交换空间的 VPS)上,我重现了您的问题;更改为MAP_SHARED 会导致EPERM 错误(因为我不允许写入使用O_RDONLY 打开的支持文件)。更改为PROT_READMAP_SHARED 允许映射成功。

如果您需要修改文件中的字节,一种选择是将您要写入的文件的范围设为私有。也就是说,munmap 并使用 MAP_PRIVATE 重新映射您打算写入的区域。当然,如果您打算写入整个文件,那么您需要 8GB 内存。

或者,您可以将1 写入/proc/sys/vm/overcommit_memory。这将允许映射请求成功;但是,请记住,如果您真的尝试使用全部 8GB 的​​ COW 内存,您的程序(或其他一些程序!)将被 OOM 杀手杀死。

【讨论】:

  • hmm.. Mat 设法将 8*3G (23G) 映射到 16G 的虚拟空间。所以它并没有真正保留内存..
  • @yi_H,默认情况下,Linux 内核允许您超出物理+交换一定百分比:opsmonkey.blogspot.com/2007/01/linux-memory-overcommit.html 根据您的系统配置,您可能会将设置设置为更严格的配置。或者,如果您只有 4G 总量,您可能会超出限制。
  • 我知道,看看他写了什么。
  • 是的,我注意到了 O_RDONLY 和 PROT_WRITE。但是,我需要交换映射文件的字节而不修改它。我尝试使用 PROT_READ 和 MAP_PRIVATE 并没有工作。谢谢你的回答
  • @Emer,请参阅我的编辑。如果需要交换 8GB 的​​文件,则需要 8GB 的​​内存+交换来存储结果。
【解决方案2】:

Linux(显然还有一些其他 UNIX 系统)具有用于 mmap(2)MAP_NORESERVE 标志,可用于显式启用交换空间过度使用。当您希望映射的文件大于系统上可用的可用内存量时,这会很有用。

这在与MAP_PRIVATE 一起使用时特别方便,并且只打算写入内存映射范围的一小部分,否则会触发整个文件的交换空间预留(或导致系统返回ENOMEM ,如果尚未启用系统范围的过度使用并且您超出了系统的可用内存)。

需要注意的问题是,如果您确实写入该内存的大部分,惰性交换空间预留可能会导致您的应用程序消耗所有空闲 RAM 并在系统上进行交换,最终触发 OOM 杀手( Linux)或导致您的应用收到SIGSEGV

【讨论】:

    【解决方案3】:

    您没有足够的虚拟内存来处理该映射。

    例如,我这里有一台 8G RAM 和 ~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
    

    因此,请确保您有足够的虚拟内存来保存该映射(即使您只触及该文件中的几页)。

    【讨论】:

    • 能不能用8+8的虚拟空间来映射两个8G的文件?即使某些页面可能永远不会被加载或修改(在这种情况下可以简单地删除它们),它需要空间听起来有点奇怪。
    • 我可以mmap 使用 (8G+8G) 虚拟内存提供许多小于 16G 的文件(三个文件高达 ~ 23G)。我无法仅使用 8G 映射单个 >8G 文件。
    • 感谢您的测试。有没有办法计算映射文件所需的确切虚拟内存量?还是取决于许多因素?
    • @Mat, 0 不是“总是过度使用”; 1 是
    • @bdonlan:你是对的,对不起 - 整个评论都是错误的
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多