在 windows 上调整内存映射文件的大小而不使指针无效

Resize a memory mapped file on windows without invalidating pointers

我想在 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...    
}

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

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

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

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

specifies that when a view of the file is mapped into a process address space, the entire range of pages is reserved for later use by the process rather than committed. Reserved pages can be committed to subsequent calls to the VirtualAlloc function. After the pages are committed, they cannot be freed or decommitted with the VirtualFree function.

代码可能如下所示:

#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

This attribute has no effect for file mapping objects that are backed by executable image files or data files (the hfile parameter is a handle to a file).

对于这个(并且只有这个)案例存在未记录的 API:

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

此 API 允许您扩展部分大小(和支持的文件)。此外,在这种情况下,SectionHandle 必须具有 SECTION_EXTEND_SIZE 访问权限,但 CreateFileMapping 创建的节句柄没有此访问权限。所以我们只需要将 NtCreateSection here, then we need use ZwMapViewOfSection api 与 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);
        }
    }
}