1

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

#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
}
4

2 回答 2

3

在这方面,Windows 与 Linux 和其他类 Unix 系统不同。它为每个FILE_OBJECT. 当最后一个句柄关闭时,文件系统取消链接文件并将其切换到“已删除”状态。同时,FILE_OBJECT在引用计数降至零之前,可能会在“已删除”状态下存活更长时间。

从这里引用:

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

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

您可以使用以下程序观察此行为。它执行以下操作:

  • 创建一个大小等于 RAM 量的文件,使用FILE_FLAG_DELETE_ON_CLOSE.
  • 将其映射到内存中。
  • 通过关闭文件的最后一个句柄来删除文件。
  • 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;
}
于 2018-08-02T09:00:55.340 回答
2

FILE_FLAG_DELETE_ON_CLOSE遵循不幸的 Windows 传统,将取消链接操作称为“删除”。实际上,该标志只会在文件关闭时导致文件从指定目录取消链接。

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

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

于 2012-06-19T10:50:26.200 回答