FILE_FLAG_DELETE_ON_CLOSE和内存映射文件

时间:2012-06-19 10:41:43

标签: c++ winapi memory-mapped-files

并不是说它特别有用,但我很好奇为什么以下工作,是因为即使文件被删除后页面仍然在内存中?在这种情况下,如果页面被换出,数据将丢失?

#include <iostream>
#include <memory>
#include <windows.h>

int main()
{
    typedef std::unique_ptr<void, decltype(&CloseHandle)> Handle;
    typedef std::unique_ptr<void, decltype(&UnmapViewOfFile)> View;

    View v(nullptr, UnmapViewOfFile);

    {
        Handle h(CreateFile(
            L"test",
            GENERIC_READ | GENERIC_WRITE,
            0,
            nullptr,
            CREATE_ALWAYS,
            FILE_FLAG_DELETE_ON_CLOSE,
            nullptr
        ), CloseHandle);

        // write something so CreateFileMapping succeeds
        DWORD sz;
        WriteFile(h.get(), "hello world", 12, &sz, nullptr);

        Handle m(CreateFileMapping(
            h.get(),
            nullptr,
            PAGE_READWRITE,
            0, 0,
            nullptr
        ), CloseHandle);

        v.reset(MapViewOfFile(
            m.get(),
            FILE_MAP_WRITE,
            0, 0,
            0
        ));

        char c;
        std::cin >> c; // File is still in folder
    }

    char c;
    std::cin >> c; // No file!

    std::cout << static_cast<char*>(v.get()); // Still writes
}

2 个答案:

答案 0 :(得分:2)

FILE_FLAG_DELETE_ON_CLOSE遵循不幸的Windows传统,将unlink操作称为“删除”。实际上,该标志仅在文件关闭时才导致文件与指定目录断开连接。

与其他操作系统一样,Windows仅为普通用户代码提供了从特定目录中取消链接文件的能力。删除始终是操作系统的决定,当文件无法以任何方式被引用时发生。

如果你看一下,你会看到文件实际上已从目录中取消链接,但它实际上不会被删除(以及数据在磁盘上可用于重用的空间),直到它的引用计数降到零。映射包含引用。

答案 1 :(得分:2)

在这方面,Windows与Linux和其他类似Unix的操作系统不同。它为每个FILE_OBJECT维护一个单独的引用和句柄计数。当最后一个句柄关闭时,文件系统将取消链接该文件并将其切换到“已删除”状态。同时,FILE_OBJECT在“已删除”状态下可能寿命更长,直到引用计数降至零为止。

引用here

  

当您关闭文件句柄时,该文件将被删除。之后,如果   修剪视图中的所有页面并调整其用途,然后进行访问   再次,内存管理器将尝试从文件中读取它们(   现在处于“已删除”状态)。接下来会发生什么取决于   文件系统。 NTFS返回错误代码(STATUS_END_OF_FILE),其中   使内存管理器满足页面为零的页面错误。   据我所知,这种行为没有得到记录,所以将来   版本的NTFS(或其他文件系统)可能返回不同的   错误,这将导致页内异常。

这意味着一旦取消链接文件,换出的数据将丢失,并替换为零。

您可以在下面的程序中观察此行为。它执行以下操作:

  • 使用FILE_FLAG_DELETE_ON_CLOSE创建一个大小等于RAM数量的文件。
  • 将其映射到内存中。
  • 通过关闭文件的最后一个句柄来删除文件。
  • 使用0xCD模式填充文件内容。由于文件的大小与RAM一样大,因此这将迫使页面退出内存并进入文件。
  • 打印映射的前16个字节。

输出表明映射开始处的数据全为零-数据已丢失。尚不清楚是否可以将其视为错误,但到今天为止,此行为至少已存在12年,并且不太可能更改。

#include <stdio.h>
#include <stdint.h>

#include <Windows.h>

int main()
{
    LARGE_INTEGER size;
    MEMORYSTATUSEX mem = { 0 };
    mem.dwLength = sizeof mem;
    GlobalMemoryStatusEx(&mem);
    size.QuadPart = mem.ullTotalPhys;

    const HANDLE hFile = CreateFileW(
        L"file-under-test",
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
        NULL);

    if (hFile == INVALID_HANDLE_VALUE) {
        fprintf(stderr, "CreateFileW() failed\n");
        return 1;
    }

    if (!SetFilePointerEx(hFile, size, NULL, SEEK_SET)) {
        fprintf(stderr, "SetFilePointerEx() failed\n");
        return 1;
    }

    if (!SetEndOfFile(hFile)) {
        fprintf(stderr, "SetEndOfFile() failed\n");
        return 1;
    }

    const HANDLE hMap = CreateFileMappingW(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);

    if (hFile == INVALID_HANDLE_VALUE) {
        fprintf(stderr, "CreateFileMappingW() failed\n");
        return 1;
    }

    void *const view = MapViewOfFile(
        hMap,
        FILE_MAP_READ | FILE_MAP_WRITE,
        0, 0,
        (size_t) size.QuadPart);

    if (view == NULL) {
        fprintf(stderr, "MapViewOfFile() failed\n");
        return 1;
    }

    CloseHandle(hMap);
    CloseHandle(hFile); // this removes the file
    memset(view, 0xCD, (size_t) size.QuadPart);

    // Print first 16 bytes
    for (int i = 0; i < 16; ++i) {
        printf("%2d: %#x\n", i, ((const volatile unsigned char *) view)[i]);
    }

    return 0;
}