繁体   English   中英

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

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

编辑:我知道最稳定的方法是更改​​应用程序的其余部分以允许使所有文件数据指针无效,但是此解决方案可能是反映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 ,然后我们需要使用ZwMapViewOfSection api( AllocationType = MEM_RESERVEViewSize = MAX_FILE_SIZE -保留ViewSize内存区域,但不提交它,但是在调用NtExtendSection之后, 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);
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM