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

时间:2019-03-08 15:49:48

标签: 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;

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

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行为的简单方法。

1 个答案:

答案 0 :(得分:2)

解决方案取决于您使用的文件映射对象是由操作系统分页文件(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,然后需要将ZwMapViewOfSection api与AllocationType = MEM_RESERVEViewSize = MAX_FILE_SIZE-保留ViewSize的内存区域,但不提交,但是调用NtExtendSection后,视图中的有效数据(提交页面)将自动扩展。 在获胜8.1之前,对于将MapViewOfFile分配类型传递给MEM_RESERVE的{​​{1}}并没有这种功能,但是从获胜8(或8.1)开始存在未记录的标记ZwMapViewOfSection这个。

通常,演示代码如下:

FILE_MAP_RESERVE