【问题标题】:Linux memory mapped file consuming more disk than expectedLinux 内存映射文件消耗的磁盘比预期的要多
【发布时间】:2020-11-25 14:48:04
【问题描述】:

上下文: 我在使用 ACE_Mem_Map 创建的代码中使用内存映射文件。观察到内存映射文件消耗的磁盘空间比预期的要多。

场景: 我有一个包含 15KB 字符数组的结构。我已经为这个结构的数组创建了一个内存映射文件,文件大小约为 2GB。

  1. 如果我尝试访问 char 数组的几个字节(比如 256),则消耗的文件大小显示为 521 MB,但文件系统显示的实际磁盘使用量(使用 df -h)超过 3GB。
  2. 如果我访问内存的所有字节,则文件大小和磁盘使用量均显示为 2 GB。

环境: 操作系统:Oracle Linux 7.3 内核版本:3.10.0/4.1.12

代码:

#include<ace/Mem_Map.h>
#include <stdio.h>

#define TEST_BUFF_SIZE 15*1024

typedef struct _test_struct_ {
    char test[TEST_BUFF_SIZE];

    _test_struct_() {
        reset();
    }

    void reset() {
        /* Issue replicating */
        memset(test, '\0', 256);

        /* Issue not replicating */
        memset(test, '\0', TEST_BUFF_SIZE);
    }
}TestStruct_t;

int main(int argc, char *argv[]) {

    if(3 != argc) {
        printf("Usage: %s <num of blocks> <filename>\n",
                argv[0]);
        return -1;
    }
    ACE_Mem_Map map_buf_;

    size_t num_of_blocks = strtoull(argv[1], NULL, 10);

    size_t MAX_SIZE = num_of_blocks*sizeof(TestStruct_t);

    char* mmap_file_name = argv[2];

    printf("num_of_blocks[%llu], sizeof(TestStruct_t)[%llu], MAX_SIZE[%llu], mmap_file_name[%s]\n",
            num_of_blocks,
            sizeof(TestStruct_t),
            MAX_SIZE,
            mmap_file_name);

    TestStruct_t *base_addr_;

    ACE_HANDLE fp_ = ACE_OS::open(mmap_file_name,O_RDWR|O_CREAT,
            ACE_DEFAULT_OPEN_PERMS,0);

    if (fp_ == ACE_INVALID_HANDLE)
    {
        printf("Error opening file\n");
        return -1;
    }
    map_buf_.map(fp_,MAX_SIZE,PROT_WRITE,MAP_SHARED);

    base_addr_ = (TestStruct_t*)map_buf_.addr();
    if (base_addr_ == MAP_FAILED)
    {
        printf("Map init failure\n");
        ACE_OS::close(fp_);
        return -1;
    }

    printf("map_buf_ size[%llu]\n",
            map_buf_.size());

    for(size_t i = 0; i < num_of_blocks; i++) {
        base_addr_[i].reset();
    }

    return 0;
}

谁能解释一下为什么会发生场景 1??

注意:在场景 1 中,如果我复制生成的 mmap 文件然后删除该副本,那么额外的 2.5GB 磁盘空间将被释放。不知道原因

【问题讨论】:

  • C 结构不能有成员函数。
  • @JohnBollinger,即使我删除了成员函数,问题仍在复制
  • 即使没有成员函数,关键是这不是 C 问题。 ACE 是一个 C++ 工具包,所提供的代码的其他方面也特定于 C++。事实上,由于程序使用 ACE 来映射文件,而不是直接调用 mmap,所以目前的问题是 ACE 特有的。
  • 当您谈论实际的“文件大小”时,您是如何衡量的? ls?
  • 我正在使用 du 和 stat 命令来验证文件大小

标签: c++ linux mmap ace


【解决方案1】:

我将你的程序“升级”到接近 C 并减去任何 ACE 并得到这个:

$ ./a.out 32 fred
num_of_blocks[32], sizeof(TestStruct_t)[15360], MAX_SIZE[491520], mmap_file_name[fred]
Bus error: 10

这几乎是意料之中的。 Mmap 不会扩展映射文件的大小,因此当您尝试引用未填充的部分时会产生地址错误。 因此,答案是无论 ACE.map 做什么,它都可能会调用 ftruncate(2) 之类的东西来将文件扩展为您作为参数提供的大小。 @John Bollinger 通过询问 how are you measuring that: ls 或 du 来暗示这一点。你应该使用后者。 反正C版差不多:

#include <sys/mman.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

#define TEST_BUFF_SIZE 15*1024

typedef struct _test_struct_ {
    char test[TEST_BUFF_SIZE];

    _test_struct_() {
        reset();
    }

    void reset() {
        /* Issue replicating */
        memset(test, '\0', 256);

        /* Issue not replicating */
        memset(test, '\0', TEST_BUFF_SIZE);
    }
}TestStruct_t;

int main(int argc, char *argv[]) {

    if(argc < 3) {
        printf("Usage: %s <num of blocks> <filename>\n",
                argv[0]);
        return 1;
    }
    void *buf;

    size_t num_of_blocks = strtoull(argv[1], NULL, 10);

    size_t MAX_SIZE = num_of_blocks*sizeof(TestStruct_t);

    char* mmap_file_name = argv[2];

    printf("num_of_blocks[%zu], sizeof(TestStruct_t)[%zu], MAX_SIZE[%zu], mmap_file_name[%s]\n",
            num_of_blocks,
            sizeof(TestStruct_t),
            MAX_SIZE,
            mmap_file_name);


    int fp = open(mmap_file_name,O_RDWR|O_CREAT,0666);

    if (fp == -1)
    {
        perror("Error opening file");
        return 1;
    }
    /*SOMETHING CLEVER*/
    switch (argc) {
    case 3:
        break;
    case 4:
        if (ftruncate(fp, MAX_SIZE) != 0) {
            perror("ftruncate");
            return 1;
        }
        break;
    case 5:
        if (lseek(fp, MAX_SIZE-1, SEEK_SET) != MAX_SIZE-1 ||
            write(fp, "", 1) != 1) {
            perror("seek,write");
            return 1;
        }
    }
    void *b = mmap(0, MAX_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fp, 0);
    if (b == MAP_FAILED)
    {
        perror("Map init failure");
        return 1;
    }
    TestStruct_t *base_addr = (TestStruct_t *)b;

    for(size_t i = 0; i < num_of_blocks; i++) {
        base_addr[i].reset();
    }

    return 0;
}

SOMETHING CLEVER 位允许您使用空文件 (argc == 3)、使用 ftruncate (argc == 4) 扩展它,或者使用 lseek && write (argc == 5) 扩展它。

在 UNIX-y 系统上,ftruncate 可能会也可能不会为您的文件保留空间;没有保留空间的加长文件称为 sparce。几乎普遍, lseek && write 将创建一个稀疏文件,除非您的系统不支持。

sparce 文件将在您写入时分配实际的磁盘块,但是,如果它失败,它会传递一个信号,而预分配的则不会。 底部的循环遍历整个范围,因此文件将始终增长;减少该循环,您可以查看这些选项是否会对您的系统产生影响。

【讨论】:

  • 在我的例子中,正在生成一个稀疏文件。问题是,分配给文件的磁盘块数与文件系统消耗的磁盘块数不匹配。假设,给文件的最大大小是 2GB。该文件消耗了 521 MB 的磁盘空间,如 du -sh 和 stat 输出所示。但是,如果我使用 df -h 看到 fs 上的磁盘空间,则表明消耗了 ~3GB,远远超过分配的 2GB。这背后的原因是什么?注意:在注释行 memset(test, '\0', TEST_BUFF_SIZE); 和 argc=4 或 5 之后,您共享的代码也可以重现该问题
  • 将 TEST__BUFF_SIZE 乘以 10,然后将 arg1 除以 10;我认为您的文件系统分配大小就是那个问题。对不起另一个,我的 ubuntu 20 不这样做。
  • 我正在使用 XFS 文件系统。你能分享一下你使用的是哪个文件系统吗??
  • ubuntu 20 上的 xfs4。我有另一个想法;你的linux有可能强制使用大页面吗?这里有一些文档:oracle-base.com/articles/linux/…,请注意有关强制大页面的内容。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-26
  • 2015-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-11
相关资源
最近更新 更多