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;
}

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

我设法使用 ost: : iostream: : map _ file 映射整个文件,但是我想使用 C 和系统调用来完成这项工作。我的代码有什么问题吗?

54412 次浏览

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

例如,我有一台机器,它有8G RAM 和 ~ 8G 交换机(所以总共有16G 的虚拟内存可用)。

如果我在 VirtualBox 的 ~ 8G 快照上运行你的代码,它工作得很好:

$ 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

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

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

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

对于仅使用 PROT_WRITE的平台,这种方法适用于 x86,因为 x86不支持只写映射,但在其他平台上可能会导致 Segfault。请求 PROT_READ|PROT_WRITE-或者,如果您只需要读取,PROT_READ

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

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

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

Linux (显然还有其他一些 UNIX 系统)具有 Mmap (2)MAP_NORESERVE标志,可以使用它显式地启用交换空间重新提交。当您希望映射的文件大于系统上可用的可用内存量时,这可能非常有用。

这在与 MAP_PRIVATE一起使用时特别方便,并且只打算写入内存映射范围的一小部分,因为这将触发整个文件的交换空间保留(或者导致系统返回 ENOMEM,如果系统范围内的超额提交没有启用,并且您超出了系统的空闲内存)。

需要注意的问题是,如果你写入了很大一部分内存,延迟交换空间保留可能会导致你的应用程序消耗掉系统上所有的空闲内存和交换空间,最终触发 OOM 杀手(Linux)或导致你的应用程序接收 SIGSEGV