【问题标题】:Reference counting issue of MapViewOfFile on WindowsWindows 上 MapViewOfFile 的引用计数问题
【发布时间】:2015-11-01 04:21:09
【问题描述】:

看来MapViewOfFile增加了文件映射内核对象的引用计数。

引用自MapViewOfFile的MSDN描述:

文件映射对象的映射视图维护内部引用 对象,并且文件映射对象直到全部关闭 对它的引用被释放。因此,要完全关闭文件 映射对象,应用程序必须取消映射文件的所有映射视图 通过调用UnmapViewOfFile映射对象并关闭文件映射 通过调用CloseHandle 处理对象。可以调用这些函数 任何顺序。

另外,来自Windows via C/C++,第 5 版

前面的代码显示了“预期”的操作方法 内存映射文件。但是,它没有显示的是系统 增加文件对象和文件映射的使用计数 调用MapViewOfFile时的对象...

尽管如此,我的实际测试表明相反。我在 Windows 10 64 位上使用 Visual Studio 2015。测试程序如下:

#include <windows.h>

int main() {
  HANDLE h = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 128, "test");
  void* p_memory = MapViewOfFile(h, FILE_MAP_WRITE, 0, 0, 0);
  CloseHandle(h);
  h = OpenFileMappingA(FILE_MAP_WRITE, FALSE, "test");
  DWORD dw = GetLastError(); // ERROR_FILE_NOT_FOUND
}

OpenFileMapping 调用失败,最后一个错误 ERROR_FILE_NOT_FOUND。当我删除CloseHandle 呼叫时,一切都会好起来的。这意味着CloseHandle 调用会消除文件映射内核对象的最后引用计数并将其销毁。这反过来意味着MapViewOfFile 实际上并没有增加对象的引用计数。

我想确定发生了什么,以及MapViewOfFile 在文件映射内核对象的引用计数方面的确切语义是什么。

【问题讨论】:

  • 也许在第一次真正打开映射之前不增加句柄使用量是一种优化?
  • 我怀疑文档中的暗示(文件映射对象在最后一个直接句柄关闭后仍然可以使用和/或重新打开)是无意的,虽然你确实有取消映射视图以释放所有相关的内核资源,这并不意味着在您实际完成使用映射之前关闭句柄是安全的。 (无论如何,如果您只是保持手柄打开,那么行为是明确的,您无需担心。)
  • @GSerg 我试图实际写入映射视图,然后关闭句柄。但问题依然存在。

标签: c++ windows winapi memory-mapped-files reference-counting


【解决方案1】:

您可以通过使用文件作为后备存储而不是分页文件来使其更具说服力:

int main() {
    const char* path = "mmf.bin";
    DeleteFile(path);
    HANDLE hFile = CreateFile(path, GENERIC_READ | GENERIC_WRITE, 
        FILE_FLAG_DELETE_ON_CLOSE,
        NULL, CREATE_NEW, 0, NULL);
    HANDLE h = CreateFileMappingA(hFile, NULL, PAGE_READWRITE, 0, 128, "test");
    int* p_memory = (int*)MapViewOfFile(h, FILE_MAP_WRITE, 0, 0, 128);
    CloseHandle(h);
    DWORD attr = GetFileAttributes(path);
    if (attr != INVALID_FILE_ATTRIBUTES) puts("File still exists");
    else puts("File is gone");
}

输出:文件已消失

所以“系统增加文件对象的使用计数”绝对是不正确的。而且我认为您反驳了它增加了文件映射对象的使用计数。不知道该怎么做,Richter 不会经常弄错。这本书的勘误表中也没有任何内容。它可能在早期版本的 Windows 中以这种方式工作,不确定,因为我从来没有故意弄错这个。我们必须坚持 SDK 文档的实际内容:

在所有使用它的进程都使用 CloseHandle 函数关闭其句柄之前,共享文件映射对象不会被销毁。

【讨论】:

  • 我认为一个内核对象如果不能被打开就会被有效地销毁。
  • 很奇怪。如果您映射了超过 0 个字节,它的行为是否仍然相同?如果是这样,随着备份文件消失,我想知道如果你写入映射的地址空间会发生什么?
  • @Lingxi:看你的意思。如果它仍然在消耗系统资源,即使我们看不到它们,也不会那么多。
  • 如果你仔细看,你会发现我实际上映射了128个字节,而不是0个字节。文件映射内核对象没了后写操作也OK了。
  • “我认为如果无法打开内核对象,它就会被有效地破坏”,不一定。无法创建对象的新句柄并不一定意味着现有句柄无效。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多