【问题标题】:Are MapViewOfFile memory mappings reused?MapViewOfFile 内存映射是否被重用?
【发布时间】:2016-11-21 04:58:48
【问题描述】:

如果我在同一个进程中为同一个文件创建 2 个单独的映射,指针会被共享吗?

换句话说:

LPCTSTR filename = //...

HANDLE file1 = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0);
HANDLE fileMapping1 = CreateFileMapping(file1, NULL, PAGE_READONLY, 0, 0, 0);
void* pointer1 = MapViewOfFile(fileMapping1, FILE_MAP_READ, 0, 0, 0);

CloseHandle(fileMapping1);
CloseHandle(file1);

HANDLE file2 = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0);
HANDLE fileMapping2 = CreateFileMapping(file2, NULL, PAGE_READONLY, 0, 0, 0);
void* pointer2 = MapViewOfFile(fileMapping2, FILE_MAP_READ, 0, 0, 0);

CloseHandle(fileMapping2);
CloseHandle(file2);

pointer1 是否会等于 pointer2

我问的原因是我有几个线程需要在一个大 (300+MB) 文件中搜索,我想为此使用内存映射。但是该进程需要能够在旧的 32 位 xp 机器上运行,所以如果每个线程在虚拟内存中分配自己的副本,那么我可能会耗尽内存。

【问题讨论】:

  • 由于同一个文件支持的文件映射保证在一个进程中是一致的(远程文件除外),我不明白为什么pointer1不能指向同一个内存块作为pointer2。事实上,两个指针相同是实现一致性保证的最直接的方式。但是,无论哪种方式(关于指针的身份)都没有书面保证。
  • 没有理由映射会导致相同的指针:分别映射/取消映射,后端的不同句柄。预计您总是会得到两个不同的指针被映射到相同的物理内存。
  • 另外,在您的代码 sn-p 中,您应该希望 file2 成为获取 fileMapping2 的参数,而不是 file1(复制/粘贴的东西)。下一行也是一样。
  • @RomanR。是的,复制粘贴错误
  • @Roman MapViewOfFile 特别指出“源自同一文件支持的任何文件映射对象的文件视图是一致的”。文件映射对象和文件句柄都不需要共享。我也不明白您评论中的部分内容:“您总是将两个不同的指针映射到同一个物理内存。”

标签: winapi memory-mapped-files


【解决方案1】:

msdn has documented it 行间:

如上所述,您可以拥有相同的多个视图 内存映射文件,它们可以重叠。但是映射两个呢 同一个内存映射文件的相同视图?学会如何做之后 取消映射文件的视图,您可以得出这样的结论: 在一个进程中不可能有两个相同的视图 因为他们的基地址是一样的,而你不能 来区分它们。这不是真的。请记住,基础 MapViewOfFileMapViewOfFileEx 返回的地址 函数不是文件视图的基地址。相反,它是 视图开始的进程中的基地址。 所以映射两个 相同内存映射文件的相同视图将产生两个视图 具有不同的基地址,但仍然具有相同的视图 内存映射文件的同一部分。

还有:

这个小练习的目的是强调 单个内存映射文件对象总是映射到一个唯一的范围 过程中的地址。 基地址因人而异 看法。出于这个原因,映射视图的基地址就是 需要取消映射视图。

【讨论】:

  • 这也解决了必须取消映射文件中具有相同起始位置但视图大小不同的 2 个视图的问题。似乎正是由于这个原因,指针肯定会有所不同。
【解决方案2】:

pointer1 会等于 pointer2 吗?

如果MapViewOfFile 为映射选择相同的地址,则指针可能相等。你不能用MapViewOfFile 来控制它,你可以用MapViewOfFileEx 来控制它(最后一个参数lpBaseAddress 在那里)。

每个单独的MapViewOfFile 可以在相同的物理数据上创建一个新的映射,因此即使您同时打开两个映射,操作系统也不需要将文件映射映射到相同的地址,从而保持数据的一致性。通过稍微修改您的代码很容易看到这一点:

HANDLE file1 = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
HANDLE fileMapping1 = CreateFileMapping(file1, NULL, PAGE_READWRITE, 0, 0, 0);
void* pointer1 = MapViewOfFile(fileMapping1, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);

//CloseHandle(fileMapping1);
//CloseHandle(file1);

HANDLE file2 = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
HANDLE fileMapping2 = CreateFileMapping(file2, NULL, PAGE_READWRITE, 0, 0, 0);
void* pointer2 = MapViewOfFile(fileMapping2, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);

INT& n1 = *((INT*) pointer1);
INT& n2 = *((INT*) pointer2);

ATLASSERT(&n1 != &n2);  // The pointers are not equal even though they point 
            // the same data!
INT n3 = 0;
n1 = 2;
n3 += n2;
n1 = 3;
n3 += n2;
ATLASSERT(n3 == 5); // That's 2+3 we wrote through n1 and read through n2

//CloseHandle(fileMapping2);
//CloseHandle(file2);

也就是说,指针等价不是您应该期望或依赖的。特别是如果您的映射很大,并且不会立即重新打开。

【讨论】:

    【解决方案3】:

    MapViewOfFile 在您的进程地址空间中找到一个足以容纳整个文件的漏洞。即使您两次传递相同的文件映射对象,我也不希望它返回相同的指针。对于不同的映射对象和不同的文件句柄,我肯定希望指针是不同的。

    在幕后,Windows 应该使用相同的“节”对象,因此两个虚拟地址空间范围应该映射到相同的物理内存。这与映射同一个文件的两个进程相同。

    要从两个线程使用相同的内存范围,一个线程必须映射文件并将指针存储在共享位置。另一个线程必须从共享位置检索该指针。您可能需要引用计数来决定何时取消映射文件(您可以通过调用 UnmapViewOfFile 来完成 - 关闭文件映射句柄不会释放该地址空间)。

    【讨论】:

      【解决方案4】:

      将使用相同的物理内存,但两个指针可能不同。在任何情况下,您都无法保证它们是相同的,即使它们在您测试时是偶然的。读作:你永远不能相信会是这样的假设。

      您正在两个不同的文件句柄上创建两个映射。顺便说一下,它们引用了同一个文件(这就是为什么将使用相同的物理内存),但它们仍然是两个不同的映射,在逻辑上没有任何关系。

      是的,在两个不同的地址上拥有相同的物理内存可能听起来不合逻辑且不合理(甚至可能是不可能的)。但是,这是完全合法的事情。

      【讨论】:

        猜你喜欢
        • 2012-04-10
        • 2014-09-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-08-08
        • 2014-07-10
        • 2015-08-09
        相关资源
        最近更新 更多