简体   繁体   English

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

[英]How to reserve memory on Windows and later map files into it?

I would like to reserve a region of memory and later map files contiguously into the reserved memory. 我想保留一个内存区域,然后将映射文件连续地映射到保留的内存中。 There may be large time gaps between mapping the files during which other functions may allocate memory from the heap. 映射文件之间可能会有很大的时间间隔,在此期间其他功能可能会从堆中分配内存。 Once mapped, a file may not be unmapped and mapped to a new memory location. 映射后,文件可能无法解映射并映射到新的内存位置。

On Linux that would be something like: 在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
}

On Windows however I can't seem to find the correct way to do this. 但是在Windows上,我似乎找不到正确的方法。 Does anyone know how this is done? 有谁知道这是怎么做的?

Edit: Since the my goal seems to be not that easy to understand. 编辑:由于我的目标似乎并不那么容易理解。 Once again simplified: 再次简化:

I would like to have memory that is memory mapped. 我想拥有映射为内存的内存。 Once the mapped memory is filled with data I would like to map a new file into memory directly after the already mapped memory to extend the mapped memory (without leaving a gap between the two memory mapped regions). 一旦映射的内存中充满了数据,我想将一个新文件直接映射到已映射内存之后的内存中,以扩展映射内存(在两个内存映射区域之间不留间隙)。

At a later program start the files may be used to restore all the data of the previous run. 在以后的程序启动时,这些文件可用于还原先前运行的所有数据。

Problem with solution with sparse files and extending existing file: Program should be able to remove files if they aren't needed any more. 稀疏文件和扩展现有文件的解决方案出现问题:如果不再需要文件,程序应该可以删除它们。 Therefore its important that the mappings are always new files. 因此,重要的是映射始终是新文件。

You may think of it more as an allocator. 您可能将其更多地视为分配器。 Memory mapped memory is needed. 需要内存映射的内存。 The library maps a block of memory and returns a pointer to a subblock. 该库映射一个内存块,并返回一个指向子块的指针。 The memory is no longer needed, it is returned to the allocator. 不再需要该内存,它将返回给分配器。 In case an entire mapping is no longer needed, the associated file is removed (The data does not need to be written by the mapping if it is no longer needed). 如果不再需要整个映射,则将删除关联的文件(如果不再需要该数据,则不需要由映射写入数据)。

You don't really need to have it reserved when you map in the files, you just need to know that you can map in the two files contiguously somewhere. 在映射文件时,实际上并不需要保留它,只需要知道可以在某个位置连续映射两个文件即可。 For example your Linux example code, being single threaded, would work just as well if you immediately unmapped the reserved region before mapping in the files. 例如,如果您在映射到文件之前立即取消映射保留区域,则单线程的Linux示例代码也可以正常工作。

On Windows, handling the possible multithreaded race condition, you can do something like: 在Windows上,处理可能的多线程竞争条件,您可以执行以下操作:

 while(1) {
     char *memory = VirtualAlloc(NULL, page_size * 2, MEM_RESERVE, 0);
     VirtualFree(memory);
     if (MapViewOfFileEx(handle1, FILE_MAP_WRITE, 0, 0, page_size, memory) == NULL
         && GetLastError() == ERROR_INVALID_ADDRESS) {
         continue;
     }
     if (MapViewOfFileEx(handle2, FILE_MAP_WRITE, 0, 0, page_size, memory + page_size) == NULL
         && GetLastError() == ERROR_INVALID_ADDRESS) {
         UnMapViewOfFile(memory);
         continue;
     }
     break;
}

my solution used UNDOCUMENTED api 我的解决方案使用UNDOCUMENTED api

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

no win32 analog for this function, but this is key point of solution. 此功能没有win32模拟,但这是解决方案的关键。 also we need use ZwMapViewOfSection but not MapViewOfFileEx (win32 shell over ZwMapViewOfSection ) because MapViewOfFileEx have less parameters than ZwMapViewOfSection - we cannot set ULONG AllocationType to MEM_RESERVE - but this is also key point. 我们还需要使用ZwMapViewOfSection而不是MapViewOfFileEx (在ZwMapViewOfSection使用Win32 shell),因为MapViewOfFileEx参数比ZwMapViewOfSection少-我们不能将ULONG AllocationType设置为MEM_RESERVE但这也是关键点。 for other tasks we can use win32 analog, but for uniformity and style i will be use NT api. 对于其他任务,我们可以使用win32模拟,但是为了统一和样式,我将使用NT api。

of course many just say that it undocumented, unsupported, etc use ntdll api direct - but really it work and here i not view solution based on win32 only. 当然,许多人只是说它是无证的,不受支持的等,直接使用ntdll api-但实际上它是有效的,在这里,我不查看仅基于win32的解决方案。 so who want can use, who not want can not use. 所以谁想要可以使用,谁不想使用就不能使用。 as is 照原样

the idea - we just reserve required large region with call to ZwMapViewOfSection (this is impossible done by MapViewOfFileEx ) and then, when need we can extent this region by call to ZwExtendSection 这个想法-我们只是通过调用ZwMapViewOfSection来保留所需的大区域(这不可能通过MapViewOfFileEx来完成),然后在需要时我们可以通过调用ZwExtendSection该区域

solution tested and worked. 解决方案经过测试和工作。

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);
    }
}

update: i discover that begin from win 8.1 we can reserve memory region with section by used also undocumented FILE_MAP_RESERVE - so need call 更新:我发现从win 8.1开始,我们可以使用未记录的 FILE_MAP_RESERVE保留带有部分的内存区域-因此需要调用

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

but in windows 7 , vista and XP - this will be not work. 但是在Windows 7,Vista和XP中-这将无法工作。 however ZwMapViewOfSection worked with MEM_RESERVE flag even in XP. 但是ZwMapViewOfSection即使在XP中也可以使用MEM_RESERVE标志。

so usual situation - frequently win32 shell have less functional compare corresponded Nt*/Zw* function. 因此,通常情况-相对于相应的Nt * / Zw *功能,win32 shell的功能较少。

and still no any win32 analog/shell for ZwExtendSection (this call extend both file and view) 并且仍然没有ZwExtendSection任何win32模拟/外壳ZwExtendSection (此调用扩展了文件和视图)

The correct solution is to rearchitecture to eliminate the requirement for adjacent mappings. 正确的解决方案是重新架构,以消除对相邻映射的需求。

Depending on your exact needs, one approach would be to use a single sparse file ( as described here ) whose initial length is equal to the amount of address space you want to reserve. 根据您的确切需求,一种方法是使用单个稀疏文件( 如此处所述 ),其初始长度等于您要保留的地址空间量。 Because the file is sparse, only the blocks that are actually used take up space on disk. 因为文件稀疏,所以只有实际使用的块才占用磁盘空间。

Failing that, you may need to change the underlying algorithms that are processing the data so that they no longer depend on the memory being contiguous. 如果失败,则可能需要更改正在处理数据的基础算法,以使它们不再依赖于连续的内存。 This usually isn't as hard as it may sound. 这通常并不像听起来那样难。


One (not at all proper!) alternative would be to hook the VirtualAlloc function so that you can make it block as necessary. 一种方法(根本不合适!)是挂接VirtualAlloc函数,以便您可以根据需要将其阻止。 That would allow you to manipulate your reserved memory range in an effectively atomic manner - you can free it, map part of it, and then re-reserve the rest, similar to Ross Ridge's answer, without any concern that another thread will allocate the memory while you are doing so. 这将使您能够以有效的原子方式操作保留的内存范围-您可以释放它,映射它的一部分,然后重新保留其余部分,这与Ross Ridge的答案类似,而不必担心另一个线程会分配该内存当您这样做时。

(That wouldn't protect you from device drivers, but AFAIK it is extremely rare for a device driver to spontaneously allocate memory in user address space.) (这不能保护您免受设备驱动程序的侵害,但是AFAIK很少有设备驱动程序自发地在用户地址空间中分配内存。)

NB: I'm assuming here that using a single file isn't acceptable for some reason. 注意:我在这里假设由于某种原因,使用单个文件是不可接受的。 If a single file will do, you should use a sparse file as suggested above. 如果可以使用单个文件,则应使用上面建议的稀疏文件。 If for some reason a single file will do but using a sparse file isn't an option, I would recommend RbMm's approach over this one - neither is a good solution, but my best guess is that this one is marginally more risky. 如果出于某种原因只能使用一个文件但不能使用稀疏文件,则我建议使用RbMm的方法来解决这个问题-都不是一个好的解决方案,但我最好的猜测是此方法的风险要大一些。 (It is certainly more difficult.) (这肯定更困难。)

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

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