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

tly*_*tly 2 c++ windows winapi file-mapping

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

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

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

reserved_pages_ptr = (char*)VirtualAlloc(nullptr, MAX_FILE_SIZE, MEM_RESERVE, PAGE_NOACCESS);
base_address = reserved_pages_ptr;
Run Code Online (Sandbox Code Playgroud)

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

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...
}
Run Code Online (Sandbox Code Playgroud)

然后我可以使用以下方法映射视图MapViewOfFileEx

data_ = (char*)MapViewOfFileEx(filemapping_handle, ... , base_address);

if (data_ != base_address)
{
    //I hope this also never happens...    
}
Run Code Online (Sandbox Code Playgroud)

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

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

RbM*_*bMm 5

解决方案取决于您使用的文件映射对象是由操作系统分页文件(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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但从SEC_RESERVE

此属性对于由可执行映像文件或数据文件支持的文件映射对象没有影响(hfile 参数是文件的句柄)。

对于这种(也只有这种)情况,存在未记录的 API:

NTSYSCALLAPI
NTSTATUS
NTAPI
NtExtendSection(
    _In_ HANDLE SectionHandle,
    _Inout_ PLARGE_INTEGER NewSectionSize
    );
Run Code Online (Sandbox Code Playgroud)

此 API 允许您扩展部分大小(和支持的文件)。另外,在这种情况下SectionHandle必须具有SECTION_EXTEND_SIZE访问权限,但CreateFileMapping创建一个没有此访问权限的节句柄。所以我们只需要在这里使用NtCreateSection,然后我们需要使用ZwMapViewOfSectionapi 和AllocationType = MEM_RESERVE-ViewSize = MAX_FILE_SIZE这个保留的ViewSize内存区域但不提交它,但在调用NtExtendSection视图中的有效数据(提交页面)后将自动扩展。在 win 8.1 之前,MapViewOfFile传递MEM_RESERVE分配类型 to的功能不存在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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)