FILE_FLAG_DELETE_ON_CLOSE和内存映射文件
FILE_FLAG_DELETE_ON_CLOSE and 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
}
FILE_FLAG_DELETE_ON_CLOSE
遵循了不幸的Windows传统,即将取消链接操作称为"删除"。实际上,该标志只会导致文件在关闭时从指定目录取消链接。
与其他操作系统一样,Windows 仅允许普通用户代码取消文件与特定目录的链接。删除始终是操作系统的决定,当无法再以任何方式引用文件时发生。
如果您查看,您会发现该文件实际上已从目录中取消链接,但它实际上不会被删除(并且数据在磁盘上占用的空间可供重用),直到其引用计数降至零。映射包含引用。
在这方面,Windows与Linux和其他Unix类产品不同。它为每个FILE_OBJECT
维护单独的引用和句柄计数。当最后一个句柄关闭时,文件系统将取消文件的链接并将其切换到"已删除"状态。同时,FILE_OBJECT
在"已删除"状态下的寿命可能会更长,直到引用计数降至零。
从这里引用:
关闭文件句柄时,文件将被删除。在此之后,如果 视图中的任何页面都会被修剪和重新调整用途,然后访问 同样,内存管理器将尝试从文件中读取它们(这 现在处于"已删除"状态)。接下来会发生什么取决于 文件系统。NTFS返回一个错误代码(
STATUS_END_OF_FILE
),其中 导致内存管理器满足零页的页面错误。 据我所知,这种行为没有记录在案,所以未来 NTFS版本(或其他文件系统)可能会返回不同的 错误,这将改为导致页面内异常。
这意味着一旦文件取消链接,交换出的数据将丢失并替换为零。
您可以使用下面的程序观察此行为。它执行以下操作:
- 创建一个大小等于 RAM 量 的文件,其中包含
- 将其映射到内存中。
- 通过关闭文件的最后一个句柄来删除文件。
- 用
0xCD
模式填充文件内容。由于文件与 RAM 一样大,这应该会强制页面从内存中取出并放入文件中。 - 打印映射的前 16 个字节。
FILE_FLAG_DELETE_ON_CLOSE
。输出显示映射开始时的数据全部为零 - 数据已丢失。目前尚不清楚这是否可以被视为错误,但截至今天,这种行为至少已有 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() failedn");
return 1;
}
if (!SetFilePointerEx(hFile, size, NULL, SEEK_SET)) {
fprintf(stderr, "SetFilePointerEx() failedn");
return 1;
}
if (!SetEndOfFile(hFile)) {
fprintf(stderr, "SetEndOfFile() failedn");
return 1;
}
const HANDLE hMap = CreateFileMappingW(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
fprintf(stderr, "CreateFileMappingW() failedn");
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() failedn");
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: %#xn", i, ((const volatile unsigned char *) view)[i]);
}
return 0;
}
- 添加静态constexpr成员是否会更改结构/类的内存映射
- C/C++ - 查询平台相关的换行符(用于内存映射文件)
- 字符串共享内存映射的向量
- 确保内存映射页位于内存中
- 我可以将新的 std::tuple 放入内存映射区域,并在以后读回吗?
- 内存映射文件访问非常慢
- 内存映射C++中的流数据
- 我正在尝试创建一个C++映射,该映射在boost内存映射文件中具有向量值
- 内存映射区域初始数据
- 内存映射 IO 概念详细信息
- 为什么 du -sh 输出错误大小的内存映射文件
- 您可以将C 对象映射到内存映射的外围设备吗?
- 将 Android 内存映射到具有零拷贝的 GraphicBuffer
- 在内存映射时写入内存映射的文件
- 如何使用 Boost 内存映射解析 C++ 中的 CSV?
- Growing Boost.使用单个写入器的进程间内存映射文件
- 调整窗口内存映射文件的大小,而不会使指针失效
- Winapi:是否需要在可执行内存映射的文件上调用FlushInstructionCache
- 与从C++到C#的内存映射文件共享链式结构
- 在 C++ 和 C# 之间共享内存映射文件结构