调整窗口内存映射文件的大小,而不会使指针失效



我想在Windows上调整内存映射文件的大小,而不会使从上次调用MapViewOfFileEx中检索到的指针无效。这样,指向整个应用程序中存储的任何文件数据的所有指针都不会因调整大小操作而失效。

我找到了解决问题的方法,但我不确定这种方法是否真的保证在所有情况下都有效。

这是我的方法: 我保留了一个大的内存区域,VirtualAlloc

reserved_pages_ptr = (char*)VirtualAlloc(nullptr, MAX_FILE_SIZE, MEM_RESERVE, PAGE_NOACCESS);
base_address = reserved_pages_ptr;

每次调整内存映射的大小时,我都会关闭旧文件映射,释放保留的页面并保留其余页面,这些页面对于文件的当前大小不需要:

filemapping_handle = CreateFileMappingW(...);
SYSTEM_INFO info;
GetSystemInfo(&info);
const DWORD page_size = info.dwAllocationGranularity;
const DWORD pages_needed = file_size / page_size + size_t(file_size % page_size != 0);
// release reserved pages:
VirtualFree(reserved_pages_ptr, 0, MEM_RELEASE);
// reserve rest pages:
reserved_pages_ptr = (char*)VirtualAlloc(
base_address + pages_needed * page_size, 
MAX_FILE_SIZE - pages_needed * page_size, 
MEM_RESERVE, PAGE_NOACCESS
);
if(reserved_pages_ptr != base_address + pages_needed * page_size)
{
//I hope this never happens...
}

然后我可以用MapViewOfFileEx映射视图:

data_ = (char*)MapViewOfFileEx(filemapping_handle, ... , base_address);
if (data_ != base_address)
{
//I hope this also never happens...    
}

这种方法是否足够稳定,以保证潜在问题永远不会发生? 我是否需要任何同步来避免多线程问题?

编辑:我知道最稳定的方法是更改应用程序的其余部分以允许使所有文件数据指针失效,但此解决方案可能是一种简单的方法,可以反映Linux上mmap的行为。

解决方案取决于您使用的文件映射对象是由操作系统分页文件(hFile 参数为INVALID_HANDLE_VALUE)支持的,还是由磁盘上的某个文件支持的。

在这种情况下,你使用的文件映射对象是由操作系统分页文件支持的,你需要使用SEC_RESERVE标志:

指定将文件的视图映射到进程时 地址空间,整个页面范围保留供以后使用 过程而不是承诺。保留页可以提交到 随后对VirtualAlloc函数的调用。页面之后 提交后,它们无法使用VirtualFree函数释放或取消提交。

代码可能如下所示:

#define MAX_FILE_SIZE 0x10000000
void ExtendInMemorySection()
{
if (HANDLE hSection = CreateFileMapping(INVALID_HANDLE_VALUE, 0, 
PAGE_READWRITE|SEC_RESERVE, 0, MAX_FILE_SIZE, NULL))
{
PVOID pv = MapViewOfFile(hSection, FILE_MAP_WRITE, 0, 0, 0);
CloseHandle(hSection);
if (pv)
{
SYSTEM_INFO info;
GetSystemInfo(&info);
PBYTE pb = (PBYTE)pv;
int n = MAX_FILE_SIZE / info.dwPageSize;
do 
{
if (!VirtualAlloc(pb, info.dwPageSize, MEM_COMMIT, PAGE_READWRITE))
{
break;
}
pb += info.dwPageSize;
} while (--n);
UnmapViewOfFile(pv);
}
}
}

但从SEC_RESERVE

此属性对受支持的文件映射对象没有影响 通过可执行图像文件或数据文件(hfile 参数为 文件的句柄)。

对于此(并且仅此)情况,存在未记录的 API:

NTSYSCALLAPI
NTSTATUS
NTAPI
NtExtendSection(
_In_ HANDLE SectionHandle,
_Inout_ PLARGE_INTEGER NewSectionSize
);

此 API 允许您扩展部分大小(和支持的文件)。此外,在这种情况下,SectionHandle必须具有SECTION_EXTEND_SIZE访问权限,但CreateFileMapping创建没有此访问权限的节句柄。所以我们只需要在这里使用NtCreateSection,然后我们需要使用ZwMapViewOfSectionAPI 与AllocationType = MEM_RESERVEViewSize = MAX_FILE_SIZE- 这个保留ViewSize内存区域但不提交它,但在调用NtExtendSection后,视图中的有效数据(提交页面)将自动扩展。 在win 8.1之前,MapViewOfFile没有这样的功能,用于传递MEM_RESERVE分配类型到ZwMapViewOfSection,而是从win 8(或8.1)开始存在未记录的标志FILE_MAP_RESERVE,允许这样做。

通常,演示代码可能如下所示:

#define MAX_FILE_SIZE 0x10000000
void ExtendFileSection()
{
HANDLE hFile = CreateFile(L"d:/ee.tmp", GENERIC_ALL, 0, 0, CREATE_ALWAYS, 0, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
HANDLE hSection;
SYSTEM_INFO info;
GetSystemInfo(&info);
// initially only 1 page in the file
LARGE_INTEGER SectionSize = { info.dwPageSize };
NTSTATUS status = NtCreateSection(&hSection, 
SECTION_EXTEND_SIZE|SECTION_MAP_READ|SECTION_MAP_WRITE, 0, 
&SectionSize, PAGE_READWRITE, SEC_COMMIT, hFile);
CloseHandle(hFile);
if (0 <= status)
{
PVOID BaseAddress = 0;
SIZE_T ViewSize = MAX_FILE_SIZE;
//MapViewOfFile(hSection, FILE_MAP_WRITE|FILE_MAP_RESERVE, 0, 0, MAX_FILE_SIZE);
status = ZwMapViewOfSection(hSection, NtCurrentProcess(), &BaseAddress, 0, 0, 0, 
&ViewSize, ViewUnmap, MEM_RESERVE, PAGE_READWRITE);
if (0 <= status)
{   
SIZE_T n = MAX_FILE_SIZE / info.dwPageSize - 1;
do 
{
SectionSize.QuadPart += info.dwPageSize;
if (0 > NtExtendSection(hSection, &SectionSize))
{
break;
}
} while (--n);
UnmapViewOfFile(BaseAddress);
}
CloseHandle(hSection);
}
}
}

最新更新