C – 内存映射B树

我正在尝试记忆映射一个巨大的文件(大约100GB),以便存储具有数十亿个键值对的B树。 内存很小,以便将所有数据保存在内存中,因此我试图从磁盘映射文件而不是使用malloc我返回并递增指向映射区域的指针。

#define MEMORY_SIZE 300000000 unsigned char *mem_buffer; void *start_ptr; void *my_malloc(int size) { unsigned char *ptr = mem_buffer; mem_buffer += size; return ptr; } void *my_calloc(int size, int object_size) { unsigned char *ptr = mem_buffer; mem_buffer += (size * object_size); return ptr; } void init(const char *file_path) { int fd = open(file_path, O_RDWR, S_IREAD | S_IWRITE); if (fd < 0) { perror("Could not open file for memory mapping"); exit(1); } start_ptr = mmap(NULL, MEMORY_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); mem_buffer = (unsigned char *) start_ptr; if (mem_buffer == MAP_FAILED) { perror("Could not memory map file"); exit(1); } printf("Successfully mapped file.\n"); } void unmap() { if (munmap(start_ptr, MEMORY_SIZE) < 0) { perror("Could not unmap file"); exit(1); } printf("Successfully unmapped file.\n"); } 

主要方法:

 int main(int argc, char **argv) { init(argv[1]); unsigned char *arr = (unsigned char *) my_malloc(6); arr[0] = 'H'; arr[1] = 'E'; arr[2] = 'L'; arr[3] = 'L'; arr[4] = 'O'; arr[5] = '\0'; unsigned char *arr2 = (unsigned char *) my_malloc(5); arr2[0] = 'M'; arr2[1] = 'I'; arr2[2] = 'A'; arr2[3] = 'U'; arr2[4] = '\0'; printf("Memory mapped string1: %s\n", arr); printf("Memory mapped string2: %s\n", arr2); struct my_btree_node *root = NULL; insert(&root, arr, 10); insert(&root, arr2, 20); print_tree(root, 0, false); // cin.ignore(); unmap(); return EXIT_SUCCESS; } 

问题是,如果请求的大小大于实际内存,我收到Cannot allocate memory错误号为12 )或者如果请求的空间位于映射区域之外,则Segmentation fault 。 有人告诉我,可以映射大于实际内存的文件。

系统是自己管理文件还是我负责只映射可用内存量,当访问更多空间时我必须取消映射并映射到另一个偏移量。

谢谢

编辑

操作系统:Ubuntu 14.04 LTS x86_64

bin / washingMachine:ELF 64位LSB可执行文件,x86-64,版本1(SYSV),动态链接(使用共享库),用于GNU / Linux 2.6.24,BuildID [sha1] = 9dc831c97ce41b0c6a77b639121584bf76deb47d,未剥离

首先,确保在64位模式下运行64位CPU。 在一个32位CPU上,进程的地址空间只有2 32字节(4千兆字节),并且没有办法同时将100 GB容纳在这个容量中 – 根本没有足够的地址。 (此外,该地址空间的一大块将被其他映射使用或由内核保留。)

其次,即使映射适合地址空间,也可能会出现问题。 映射到您的进程的内存(这也包括例如您的程序的代码和数据段,以及共享库的同上)被分成页面单元(通常在x86上大4 KB),其中每个页面需要一些元数据内核和MMU 。 这是另一种在创建大量内存映射时可能会耗尽的资源。

正如Mmap()中建议的整个大文件一样 ,您可以尝试使用MAP_SHARED 。 这可能允许内核在访问映射时懒惰地为映射分配内存,因为它知道如果内存不足,它总是可以将页面交换到磁盘上的文件。 使用MAP_PRIVATE ,内核需要在每次修改页面时分配一个新页面(因为不应该进行更改),如果系统内存和交换耗尽,这样做是不安全的。

您可能还需要在分配比物理内存更多的内存时将MAP_NORESERVE传递给mmap() ,或者将/proc/sys/vm/overcommit_memory (请参阅proc(5) )设置为1(由于系统有点丑陋) – 尽管)。

在我的系统上,与你的系统相似,具有8 GB的RAM和8 GB的交换,仅MAP_SHARED足以mmap()一个40 GB的文件。 MAP_PRIVATE以及MAP_NORESERVE也可以。

如果这不起作用,那么您可能会遇到与MMU相关的限制。 许多现代CPU架构都支持大页面 ,这些页面大于默认页面大小。 巨大页面的重点是,您需要更少的页面来映射相同数量的内存(假设大型映射),这样可以减少元数据的数量,并可以提高地址转换和上下文切换的效率。 当只使用一小部分页面时,大页面的缺点是减少了映射粒度和增加的浪费(内部碎片)。

配对MAP_SHARED和一些带有大页面的随机文件不太可能顺便使用(如果MAP_SHARED不足以解决问题)。 该文件需要在hugetlbfs中 。

MAP_HUGETLB传递给mmap()请求使用大页面进行分配(尽管它可能仅用于匿名映射,现在看来很多页面上的大页面应该是自动的)。 您可能还需要使用/proc/sys/vm/nr_hugepages/proc/sys/vm/nr_overcommit_hugepages – 请参阅此线程和内核源文件中的Documentation / vm / hugetlbpage.txt文件。

顺便编写自己的内存分配器时要注意对齐问题 。 我希望这不是太多插件,但看到这个答案 。

作为旁注,您从内存映射文件访问的任何内存必须实际存在于文件中。 如果文件小于映射并且您希望仍能访问“额外”内存,则可以使用ftruncate(2)首先增大文件。 (如果文件系统支持带文件漏洞的稀疏文件 ,这实际上可能不会在磁盘上大大增加它的大小。)

在不知道您使用的是哪种操作系统的情况下,我最好的猜测是您的操作系统不允许无限制地过度使用内存,或者将MAP_PRIVATE映射计入RLIMIT_DATA ulimit。 两者都意味着你的代码不起作用。

您基本上告诉mmap使用MAP_PRIVATE是“映射此文件,但我在映射区域中所做的任何更改,都将它们视为此程序中的本地内存分配”。 在这种情况下映射文件的技巧是,如果内存不足,您允许操作系统将页面写入磁盘。 因为你告诉操作系统不允许写出来的东西所以不能这样做。

解决方案是使用MAP_SHARED ,但请确保您了解mmap的手册页以及MAP_SHAREDfunction。 此外,请确保您只映射文件大小,或者将文件设置为您需要的大小。

另外,请阅读有关length参数的mmap手册页。 某些操作系统允许大小不是页面大小的倍数,但这是非常不可移植的,将您的大小调整到页面大小。