1

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

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

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

4

1 回答 1

3

解决方案取决于您使用的文件映射对象是由操作系统分页文件(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在这里使用,然后我们需要使用ZwMapViewOfSectionapiAllocationType = 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);
        }
    }
}
于 2019-03-08T17:28:50.783 回答