【问题标题】:mmap only works on small files?mmap 仅适用于小文件?
【发布时间】:2018-09-08 20:33:24
【问题描述】:

我正在尝试将文件从本地磁盘映射到内存,以便我的程序可以访问文件内容。当对文件调用 mmap 时(大小略低于 100kB),我在调试器中查看从 mmap 返回的地址开始的内存,内存内容与文件内容不匹配(均以十六进制查看)。这不是字节交换问题。只有内存中的前 2 个字节与实际文件匹配,其余内容不匹配。

当我对包含字符串的小文件(例如:“hello world”)重复相同的操作时,在调试器中查看的内存与文件的内容完全匹配(再次以十六进制查看)。

我尝试使用 MAP_PRIVATE 而不是 MAP_SHARED,但结果相同。我怎样才能让它与我更大的文件一起使用?

我在 Ubuntu 17.10 中使用 Eclipse 4.7.2 + CDT 并使用 GDB 进行调试。

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string>
#include <unistd.h>
#include <sys/mman.h>

int main()
{
    void* MapAddr = NULL;
    char* pData = NULL;
    struct stat FileProps;
    int FileDes = 0;
    const char* fileNameAndPath = "/home/Test/testfile.txt";

    FileDes = open(fileNameAndPath, O_RDWR);

    if (FileDes != -1)
    {
        if (fstat(FileDes, &FileProps) == 0)
        {
            MapAddr = mmap(NULL, FileProps.st_size, (PROT_READ | PROT_WRITE), MAP_SHARED, FileDes, 0);
            if (MapAddr == (void*) -1)
            {
                std::cout << "init: mmap failed" << std::endl;
                return 0;
            }
        }
    }
    pData = (char*) MapAddr;
    std::cout << pData << std::endl;
    return 0;
}

13:10:42 **** 为项目 mmapTest 构建配置调试 **** 做所有 构建文件:../src/mmapTest.cpp 调用:GCC C++ 编译器 g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"src/mmapTest.d" -MT"src/mmapTest.o" -o "src/mmapTest.o" "../ src/mmapTest.cpp" 完成构建:../src/mmapTest.cpp

构建目标:mmapTest 调用:GCC C++ 链接器 g++ -o "mmapTest" ./src/mmapTest.o
完成构建目标:mmapTest

13:10:46 构建完成(耗时 4s.438ms)

【问题讨论】:

  • 我想您知道该程序使用非标准功能。您是否出于某种原因要将文件读入映射内存而不是堆中的内存?做后者很容易,并且可以便携地完成。
  • 感谢您提供可编译的代码示例。您能否也说明您是如何重现该问题的?
  • 但是程序有效吗? nmap() 通过分页工作,调试器可能会也可能不会强制这样做。
  • JD - mmap 是非标准的,还是我正在做的其他事情?我正在尝试将 Windows 函数 CreateFileMapping 移植到 Linux。我的目标是从文件中取出数据,将其映射到内存中,然后将该内存映射到结构指针。有没有更好的方法来做到这一点?
  • @JiveDadson 它在问题中描述的平台上编译。我不明白你想表达什么样的观点,或者为什么它在这种情况下会有所帮助

标签: c++ linux memory mmap


【解决方案1】:

mmap() 实际上并没有将整个文件读入内存,也没有实际分配文件大小的 RAM 量。它只是分配足够大的虚拟地址空间以“容纳”其中的文件。

它通过使用页面错误来工作,当您尝试读取文件的某些区域时,将分配实际的 RAM(或重用其他一些页面)并将一定数量的页面的数据从文件读取到内存中。

您几乎不会使用mmap() 将整个文件加载到内存中。但是您的程序应该可以工作,无论何时您尝试读取或写入数据(来自程序,不是来自调试器),一切都会正常工作。


而且,是的,最重要的是,mmap() 的工作方式与 CreateFileMapping() 相同,因此您应该可以移植代码。

【讨论】:

  • 我想知道GDB如何加载内存而不导致页面错误?
  • 感谢 BJovke 的快速响应。让我确认它在不使用调试器的情况下工作(我将尝试将一些结构或某些东西映射到文件内容并尝试打印它们)。 BRB。
  • 所有调试器都使用专门为调试而创建的特殊 CPU 指令。调试器很可能有一个选项不会导致页面错误或由它运行引起的任何其他副作用。这个想法是模仿程序的真实工作环境。
  • 我认为在 Linux 程序的调试器中看到意外内存的问题是我正在映射一个在 Windows 应用程序中使用的文件,并且很可能是为 Windows 编码的。这听起来像是一个可能的解释吗? (即:在 Linux 中使用 mmap 映射在 Windows 中编码的文件,并看到错误的内容。在 Windows 调试器中,内存与文件内容完全匹配)。抱歉,我对不同操作系统(UTF8 等)之间的文件编码知之甚少。
  • CreateFileMapping() 最初可能会将大部分文件加载到 RAM 中,这完全取决于它的内部实现方式。如果此初始缓冲区大于您的文件,则读取整个文件。对您来说,这并不重要,当您尝试读取数据时,您总会得到您的内容。不要试图依赖mmap()CreateFileMapping() 的内部工作原理,它们都可以保证您的程序将按预期运行。
【解决方案2】:

我确定内存内容(在 GDB 调试器中查看)与实际文件内容不匹配的原因是,在运行 mmap() 之后,我映射的文件不是 ANSI 编码的。因此,调试器显示了 Linux 认为应该显示的正确数据。以 ANSI 格式保存文件(在 textpad 中)后,在 Linux 中查看的文件的二进制内容与在 Windows 中查看的二进制内容相同。无需更改代码。问题在于正在映射的文件。与其中一个 cmets 不同的是,GDB 调试器能够使用内存查看器在 mmap() 返回的地址处显示所有映射的文件数据——我已经确认了最大为 100KB 的文件。

【讨论】:

    猜你喜欢
    • 2015-11-23
    • 1970-01-01
    • 1970-01-01
    • 2021-04-18
    • 2016-04-20
    • 2014-12-09
    • 2023-03-14
    • 1970-01-01
    相关资源
    最近更新 更多