【问题标题】:mmap() cannot allocate memory when repeatedly mapping and unmapping one single page重复映射和取消映射单个页面时 mmap() 无法分配内存
【发布时间】:2021-01-11 14:12:12
【问题描述】:

我已经阅读了许多 SO(和其他)问题,但找不到对我有帮助的问题。我想一次mmap 两个文件并逐字节复制它们的内容(我知道这看起来很荒谬,但这是我最小的可重复示例)。因此,我遍历每个字节,复制它,在文件中一页大小之后,我 munmap 当前页和 mmap 下一页。在我看来,每个文件应该只需要一页(4096 字节),所以不应该有任何内存问题。

另外,如果输出文件太小,内存是通过posix_fallocate分配的,运行正常。我的硬盘驱动器内存空间不足也不是问题。

但是,当我要使用大约 140 MB 的更大文件时,我会从正在写入的输出文件中收到 cannot allocate memory 错误。你们知道这是怎么回事吗?

#include <sys/types.h>
#include <sys/mman.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <iostream>
#include <bitset>
#include <fcntl.h>
#include <sys/stat.h>
#include <math.h>
#include <errno.h>

using namespace std;

int main()
{

    char file_input[] = "medium_big_file";
    char file_output[] = "foo_output";
    int fd_input = -1;
    int fd_output = -1;
    unsigned char *map_page_input, *map_page_output;
    struct stat stat_input, stat_output;

    if ((fd_input = open(file_input, O_RDONLY)) == -1 ||
          (fd_output = open(file_output, O_RDWR|O_CREAT, 0644)) == -1) {
            cerr << "Error on open()" << endl;
            return EXIT_FAILURE;
    }

    // get file size via stat()
    stat(file_input, &stat_input);
    stat(file_output, &stat_output);
    const size_t size_input = stat_input.st_size;
    const size_t size_output = stat_output.st_size;

    const size_t pagesize = getpagesize();

    size_t page = 0;
    size_t pos = pagesize;

    if (size_output < size_input) {
      if (posix_fallocate(fd_output, 0, size_input) != 0) {
        cerr << "file space allocation didn't work" << endl;
        return EXIT_FAILURE;
      }
    }

    while(pos + (pagesize * (page-1)) < size_input) {
      // check if input needs the next page
      if (pos == pagesize) {
        munmap(&map_page_input, pagesize);
        map_page_input = (unsigned char*)mmap(NULL, pagesize, PROT_READ,
          MAP_FILE|MAP_PRIVATE, fd_input, page * pagesize);
        munmap(&map_page_output, pagesize);
        map_page_output = (unsigned char*)mmap(NULL, pagesize,
          PROT_READ|PROT_WRITE, MAP_SHARED, fd_output, page * pagesize);
        page += 1;
        pos = 0;
        if (map_page_output == MAP_FAILED) {
      cerr << "errno: " << strerror(errno) << endl;
          cerr << "mmap failed on page " << page << endl;
          return EXIT_FAILURE;
        }
      }

      memcpy(&map_page_output[pos], &map_page_input[pos], 1);

      pos += 1;
    }

    munmap(&map_page_input, pagesize);
    munmap(&map_page_output, pagesize);


    close(fd_input);
    close(fd_output);
    return EXIT_SUCCESS;
}

【问题讨论】:

    标签: c++ linux mmap


    【解决方案1】:

    循环的第一次迭代尝试取消映射从未映射过的东西,并将一个完全未初始化的指针传递给munmap。不是一次,而是两次。

    最后,munmap 需要一个指向 mmap 内存的指针,而不是一个指向 mmap 内存的指针。

    显示的代码无法检查来自munmap 的返回状态。如果是这样,它会发现对munmap 的每次调用都会失败(希望,但是如果第一次调用恰好传递了一个对齐的指针,堆栈的一部分可能最终会被取消映射,随之而来的欢闹),所以显示的代码只是不断分配越来越多的页面,并且内存不足。

    您必须修复这两个错误。

    【讨论】:

    • 好吧,至少 OP 提供了一个minimal reproducible example,干得好!
    • 的确,时代已经变成了可悲的情况:一个正常的问题值得一票,因为它是正常的,而且比其他 90% 的人都好。
    • 理论上,提出问题的人可能会查看其他问题并模仿获得更多支持的问题...
    • 只能希望……但是,过去的经验表明,这只是一种理论。
    【解决方案2】:

    您没有检查munmap 的退出代码。它失败。它失败了,因为您不需要获取地址的地址。替换:

    munmap(&map_page_input, pagesize);
    

    munmap(map_page_input, pagesize);
    

    由于 munmap 失败,每个进程的最大映射数用完了。

    【讨论】:

      【解决方案3】:

      munmap 将 mmap 返回的 value 作为第一个参数。在您的代码中, munpap 收到一个指向包含它的变量的指针,因此您实际上并没有取消映射该区域。只需删除 munmap 调用中的“&”即可。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-05-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-06-25
        • 2019-06-29
        相关资源
        最近更新 更多