如何在Windows上保留内存以及以后将文件映射到其中?

时间:2016-12-10 19:39:45

标签: c++ c windows winapi memory-mapping

我想保留一个内存区域,然后将文件连续映射到保留的内存中。映射文件之间可能存在大的时间间隔,在此期间其他功能可能从堆中分配内存。映射后,文件可能无法取消映射并映射到新的内存位置。

在Linux上就像:

#include <iostream>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <cerrno>

int main(){
    void *memory = mmap(nullptr, getpagesize() * 2,
                        PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE, -1, 0); // reserve memory
    int handle1 = ::open("1", O_CREAT | O_RDWR, S_IRWXU); // open file1
    int handle2 = ::open("2", O_CREAT | O_RDWR, S_IRWXU); // open file2
    void *data = mmap(memory, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED |MAP_FIXED, handle1, 0); // map first file into reserved memory
    void *data2 = mmap(static_cast<char *>(memory) + getpagesize(), getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, handle2, 0); // map second file into reserved memory
}

在Windows上,我似乎无法找到正确的方法来执行此操作。有谁知道这是怎么做的?

编辑: 由于我的目标似乎并不那么容易理解。再次简化:

我想要内存映射的内存。一旦映射的内存被数据填充,我想在已映射的内存之后直接将新文件映射到内存中以扩展映射的内存(不会在两个内存映射区域之间留下间隙)。

在稍后的程序启动时,可以使用这些文件来恢复上一次运行的所有数据。

使用稀疏文件和扩展现有文件的解决方案存在问题: 程序应该能够删除不再需要的文件。因此,重要的是映射始终是新文件。

您可能会将其视为分配器。需要内存映射内存。库映射一块内存并返回指向子块的指针。不再需要内存,它将返回给分配器。如果不再需要整个映射,则删除相关文件(如果不再需要,则不需要通过映射写入数据)。

3 个答案:

答案 0 :(得分:2)

当你在文件中映射时,你真的不需要保留它,你只需要知道你可以在某个地方连续映射这两个文件。例如,如果您在映射文件之前立即取消映射保留区域,那么您的Linux示例代码(单线程)也可以正常工作。

在Windows上,处理可能的多线程竞争条件,您可以执行以下操作:

A

答案 1 :(得分:1)

我的解决方案使用 UNDOCUMENTED api

NTSYSAPI NTSTATUS NTAPI ZwExtendSection ( HANDLE SectionHaqndle, PLARGE_INTEGER SectionSize );

此功能没有win32模拟,但这是解决方案的关键点。 我们还需要使用ZwMapViewOfSection但不能使用MapViewOfFileEx(win32 shell超过ZwMapViewOfSection),因为MapViewOfFileEx的参数少于ZwMapViewOfSection - 我们无法设置ULONG AllocationTypeMEM_RESERVE - 但这也是关键点。对于其他任务,我们可以使用win32模拟,但为了统一性和风格,我将使用NT api。

当然很多人只是说它没有文档,不支持等等直接使用ntdll api - 但实际上它可以正常工作,在这里我不会只查看基于win32的解决方案。所以谁想要可以使用,谁不想要不能使用。按原样

这个想法 - 我们只是通过调用ZwMapViewOfSection来保留所需的大区域(这不可能由MapViewOfFileEx完成)然后,在需要时我们可以通过调用ZwExtendSection来扩展此区域

解决方案测试和工作。

class SECTION_EX
{
    LARGE_INTEGER _CurrentSize, _MaximumSize;
    HANDLE _hSection;
    PVOID _BaseAdress;

public:
    NTSTATUS Create(POBJECT_ATTRIBUTES poa, SIZE_T InitialSize, SIZE_T MaximumSize);
    NTSTATUS Extend(SIZE_T NewSize);

    SECTION_EX()
    {
        _BaseAdress = 0;
        _hSection = 0;
    }

    ~SECTION_EX()
    {
        if (_hSection) 
        {
            if (_BaseAdress) ZwUnmapViewOfSection(NtCurrentProcess(), _BaseAdress);
            ZwClose(_hSection);
        }
    }
};

NTSTATUS SECTION_EX::Extend(SIZE_T NewSize)
{
    LARGE_INTEGER Size;
    Size.QuadPart = NewSize; 

    if (Size.QuadPart <= _CurrentSize.QuadPart)
    {
        return STATUS_SUCCESS;
    }

    if (Size.QuadPart > _MaximumSize.QuadPart)
    {
        return STATUS_SECTION_TOO_BIG;
    }

    NTSTATUS status = ZwExtendSection(_hSection, &Size);

    if (0 <= status)
    {
        _CurrentSize = Size;
    }

    return status;
}

NTSTATUS SECTION_EX::Create(POBJECT_ATTRIBUTES poa, SIZE_T InitialSize, SIZE_T MaximumSize)
{
    HANDLE hFile;
    IO_STATUS_BLOCK iosb;

    NTSTATUS status = ZwCreateFile(&hFile, FILE_GENERIC_READ|FILE_GENERIC_WRITE, poa,
        &iosb, 0, 0, FILE_SHARE_VALID_FLAGS, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_ALERT, 0, 0);

    if (0 <= status)
    {
        _MaximumSize.QuadPart = MaximumSize;

        LARGE_INTEGER Size, *pSize = &Size;
        Size.QuadPart = InitialSize;

        if (iosb.Information == FILE_OPENED)
        {
            FILE_STANDARD_INFORMATION fsi;

            if (0 <= (status = ZwQueryInformationFile(hFile, &iosb, &fsi, sizeof(fsi), FileStandardInformation)))
            {
                if (fsi.EndOfFile.QuadPart)
                {
                    pSize = 0;// in case file already exist with not zero size - use it
                }
            }
        }

        if (0 <= status)
        {
            status = ZwCreateSection(&_hSection, SECTION_ALL_ACCESS, 0, pSize, 
                PAGE_READWRITE, SEC_COMMIT, hFile);
        }

        ZwClose(hFile);

        if (0 <= status)
        {
            SECTION_BASIC_INFORMATION sbi;

            if (0 <= ZwQuerySection(_hSection, SectionBasicInformation, &sbi, sizeof(sbi), 0))
            {
                _CurrentSize = sbi.Size;// real file size in bytes, without align

                // !!! use MEM_RESERVE !!!
                // MaximumSize - will be reserved, but not all commited

                status = ZwMapViewOfSection(_hSection, NtCurrentProcess(), &_BaseAdress, 0, 
                    0, 0, &MaximumSize, ViewUnmap, MEM_RESERVE, PAGE_READWRITE);
            }
        }
    }

    return status;
}

void demoS()
{
    SECTION_EX se;
    STATIC_OBJECT_ATTRIBUTES(oa, "\\??\\c:\\***");
    // reserve 256Mb,but initially commit only 32kb or file size
    if (0 <= se.Create(&oa, 0x8000, 0x10000000))
    {
        se.Extend(0x18000);
        se.Extend(0x1e245);
        se.Extend(0x74100);
    }
}

更新:我发现从win 8.1开始,我们可以使用未记录的 FILE_MAP_RESERVE来保留带有部分的内存区域 - 所以需要调用

_BaseAdress = MapViewOfFileEx(_hSection, FILE_MAP_ALL_ACCESS|FILE_MAP_RESERVE, 0, 0, MaximumSize, 0);

但在Windows 7,vista和XP中 - 这将无法正常工作。但ZwMapViewOfSection即使在XP中也使用MEM_RESERVE标志。

通常的情况 - 经常win32 shell的功能比较少对应Nt * / Zw *功能。

并且仍然没有ZwExtendSection的任何win32模拟/ shell(此调用扩展文件和视图)

答案 2 :(得分:1)

正确的解决方案是重新构建以消除对相邻映射的要求。

根据您的确切需求,一种方法是使用单个稀疏文件(as described here),其初始长度等于您要保留的地址空间量。由于文件是稀疏的,因此只有实际使用的块会占用磁盘空间。

如果失败,您可能需要更改处理数据的基础算法,以便它们不再依赖于连续的内存。这通常并不像听起来那么难。

一个(根本不适合!)替代方法是挂钩VirtualAlloc函数,以便您可以根据需要进行阻止。这将允许您以有效的原子方式操纵您的保留内存范围 - 您可以释放它,映射其中的一部分,然后重新保留其余部分,类似于Ross Ridge的答案,而不用担心另一个线程在你这样做时会分配内存。

(这不会保护您免受设备驱动程序的攻击,但AFAIK设备驱动程序在用户地址空间中自发分配内存的情况极为罕见。)

注意:我在此假设使用单个文件由于某种原因是不可接受的。如果单个文件可以,您应该使用上面建议的稀疏文件。如果出于某种原因单个文件可以使用,但是使用稀疏文件不是一个选项,我建议使用RbMm的方法而不是良好的解决方案,但是我的最好的猜测是,这个风险稍大。 (这当然更难。)