Windows没有mremap功能吗?

3
据我了解,在Linux中,要保留一块虚拟内存,你需要使用mmap函数并指定MAP_ANONYMOUSMAP_PRIVATE参数。而在Windows中,相同的功能可以通过调用VirtualAlloc函数实现。
然而,Linux提供了一个mremap函数来重新调整内存映射的大小,该函数的man手册中描述其为:“更改虚拟地址和内存页之间的映射关系”。但是在Windows中,似乎没有相应的系统调用。实现内存重分配的方法是使用HeapAlloc函数分配新内存,并使用HeapReAlloc函数重新分配内存。对于HeapReAlloc函数,MSDN上有这样的描述:“保留内存内容的过程需要进行潜在耗时的内存复制操作。”
因此,在Windows中是否不存在像Linux中那样的"重映射"虚拟内存的方法呢?如果真的不存在,为什么会这样呢?

在空句柄上使用CreateFilemapping,然后跟随MapViewOfFile更接近于使用MAP_ANONYMOUS调用mmap。在Win32中,使用MapViewOfFileEx并指定相同的地址最接近mremap。当然,如果您坚持使用它,也可以使用VirtualAllocEx。无论如何,您都无法保证获得相同的地址(在Linux下也是如此!),因此我想尽可能接近并不重要。 - Damon
1
稍作更正:实际上在Windows下你可以获得更好的行为。如果你首先保留足够大的地址空间,然后稍后再提交它,你可以保证它位于相同的位置。尽管如此,它仍然不完全相同(但可能更好)。 - Damon
重要的不是获取相同的地址,而是不实际复制数据(即仅重新映射物理页面)。 - Dan
1
首先使用MEM_RESERVE来保留内存空间,然后稍后再使用MEM_COMMIT来分配实际需要的内存空间,这样可以确保你得到的正是你所需的。唯一需要注意的是在32位系统下可能会耗尽地址空间,如果你需要的是几百兆字节的内存(在64位系统下则没有问题,显然)。 - Damon
3个回答

3
在Windows上,可以通过Win32 API中的AWE函数系列实现对虚拟内存的细粒度控制。首先使用AllocateUserPhysicalPages进行初始分配,该函数会为您分配实际物理页面。然后,您可以使用MapUserPhysicalPages将这些物理页面映射到您之前使用VirtualAlloc保留的虚拟地址空间范围中的虚拟页面。请注意,您可以将已经映射的物理页面重新映射到不同的虚拟页面。但需要注意的是,由于同时处理物理和虚拟内存,这种方式具有不同的语义。其中一些值得提及的缺点包括需要确保不存在别名,以及实际上受限于使用本地页大小,即无法使用大页面。

1
这是使用地址窗口扩展(AWE)的工作解决方案。其思想是将起始物理页暂时映射到虚拟内存的末尾。为了做到这一点,您必须虚拟分配两倍于循环数组大小的空间。
它不像Linux mremap那样方便,但它可以工作。根据MSDN文档(https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc2),您还可以使用VirtualAlloc2中的“占位符”,但仅适用于Windows 10及更高版本。
此解决方案基于MSDN示例(https://msdn.microsoft.com/en-us/library/windows/desktop/aa366531(v=vs.85).aspx)。在运行之前,请记得在Windows中获取您的帐户的“锁定页面内存”特权。
    #include <windows.h>
    #include <stdio.h>
    #include <tchar.h>
    
    #define MEMORY_REQUESTED 1024*1024 // request a megabyte
    
    BOOL
    LoggedSetLockPagesPrivilege ( HANDLE hProcess,
                                  BOOL bEnable);
    
    BOOL
    CyclicMapUserPhysicalPages( void* VirtualAddress,
                                void* VirtualHeadAddress,
                                ULONG_PTR NumberOfPages,
                                ULONG_PTR* PageArray,
                                DWORD dwPageSize);
    
    void _cdecl main()
    {
      BOOL bResult;                   // generic Boolean value
      ULONG_PTR NumberOfPages;        // number of pages to request
      ULONG_PTR NumberOfPagesInitial; // initial number of pages requested
      ULONG_PTR *aPFNs;               // page info; holds opaque data
      PVOID lpMemReserved;            // AWE window
      SYSTEM_INFO sSysInfo;           // useful system information
      int PFNArraySize;               // memory to request for PFN array
    
      GetSystemInfo(&sSysInfo);  // fill the system information structure
    
      _tprintf(_T("This computer has page size %d.\n"), sSysInfo.dwPageSize);
    
      // Calculate the number of pages of memory to request.
    
      NumberOfPages = MEMORY_REQUESTED/sSysInfo.dwPageSize;
      _tprintf (_T("Requesting %d pages of memory.\n"), NumberOfPages);
    
      // Calculate the size of the user PFN array.
    
      PFNArraySize = NumberOfPages * sizeof (ULONG_PTR);
    
      _tprintf (_T("Requesting a PFN array of %d bytes.\n"), PFNArraySize);
    
      aPFNs = (ULONG_PTR *) HeapAlloc(GetProcessHeap(), 0, PFNArraySize);
    
      if (aPFNs == NULL)
      {
        _tprintf (_T("Failed to allocate on heap.\n"));
        return;
      }
    
      // Enable the privilege.
    
      if( ! LoggedSetLockPagesPrivilege( GetCurrentProcess(), TRUE ) )
      {
        return;
      }
    
      // Allocate the physical memory.
    
      NumberOfPagesInitial = NumberOfPages;
      bResult = AllocateUserPhysicalPages( GetCurrentProcess(),
                                           &NumberOfPages,
                                           aPFNs );
    
      if( bResult != TRUE )
      {
        _tprintf(_T("Cannot allocate physical pages (%u)\n"), GetLastError() );
        return;
      }
    
      if( NumberOfPagesInitial != NumberOfPages )
      {
        _tprintf(_T("Allocated only %p pages.\n"), NumberOfPages );
        return;
      }
    
      // Reserve the virtual memory.
    
      lpMemReserved = VirtualAlloc( NULL,
                                    MEMORY_REQUESTED*2, // NB: Twice the size
                                    MEM_RESERVE | MEM_PHYSICAL,
                                    PAGE_READWRITE );
    
      if( lpMemReserved == NULL )
      {
        _tprintf(_T("Cannot reserve memory.\n"));
        return;
      }
    
      // Cyclic Map the physical memory into the window.
      void* Head = ((char*)lpMemReserved) + MEMORY_REQUESTED - 6; // Arbitrary Head Address (must be between >= lpMemReserved and <lpMemReserved+MEMORY_REQUESTED)
      bResult = CyclicMapUserPhysicalPages( lpMemReserved,
                                            Head,
                                            NumberOfPages,
                                            aPFNs,
                                            sSysInfo.dwPageSize );
    
      if( bResult != TRUE )
      {
        _tprintf(_T("CyclicMapUserPhysicalPages failed (%u)\n"), GetLastError() );
        return;
      }
    
      sprintf((char*)Head, "Hello World");
    
      /// unmap Cyclic
      bResult = CyclicMapUserPhysicalPages( lpMemReserved,
                                            Head,
                                            NumberOfPages,
                                            NULL,
                                            sSysInfo.dwPageSize );
    
      if( bResult != TRUE )
      {
        _tprintf(_T("CyclicMapUserPhysicalPages failed (%u)\n"), GetLastError() );
        return;
      }
    
      // Map the physical memory into the window.
    
      bResult = MapUserPhysicalPages( lpMemReserved,
                                      NumberOfPages,
                                      aPFNs );
    
      if( bResult != TRUE )
      {
        _tprintf(_T("MapUserPhysicalPages failed (%u)\n"), GetLastError() );
        return;
      }
    
      if (strcmp((char const*)lpMemReserved, "World"))
      {
        _tprintf(_T("Mem Content Check failed\n") );
        return;
      }
    
      // unmap
    
      bResult = MapUserPhysicalPages( lpMemReserved,
                                      NumberOfPages,
                                      NULL );
    
      if( bResult != TRUE )
      {
        _tprintf(_T("MapUserPhysicalPages failed (%u)\n"), GetLastError() );
        return;
      }
    
      // Free the physical pages.
    
      bResult = FreeUserPhysicalPages( GetCurrentProcess(),
                                       &NumberOfPages,
                                       aPFNs );
    
      if( bResult != TRUE )
      {
        _tprintf(_T("Cannot free physical pages, error %u.\n"), GetLastError());
        return;
      }
    
      // Free virtual memory.
    
      bResult = VirtualFree( lpMemReserved,
                             0,
                             MEM_RELEASE );
    
      // Release the aPFNs array.
    
      bResult = HeapFree(GetProcessHeap(), 0, aPFNs);
    
      if( bResult != TRUE )
      {
          _tprintf(_T("Call to HeapFree has failed (%u)\n"), GetLastError() );
      }
    
    }
    
    /*****************************************************************
       LoggedSetLockPagesPrivilege: a function to obtain or
       release the privilege of locking physical pages.
    
       Inputs:
    
           HANDLE hProcess: Handle for the process for which the
           privilege is needed
    
           BOOL bEnable: Enable (TRUE) or disable?
    
       Return value: TRUE indicates success, FALSE failure.
    
    *****************************************************************/
    BOOL
    LoggedSetLockPagesPrivilege ( HANDLE hProcess,
                                  BOOL bEnable)
    {
      struct {
        DWORD Count;
        LUID_AND_ATTRIBUTES Privilege [1];
      } Info;
    
      HANDLE Token;
      BOOL Result;
    
      // Open the token.
    
      Result = OpenProcessToken ( hProcess,
                                  TOKEN_ADJUST_PRIVILEGES,
                                  & Token);
    
      if( Result != TRUE )
      {
        _tprintf( _T("Cannot open process token.\n") );
        return FALSE;
      }
    
      // Enable or disable?
    
      Info.Count = 1;
      if( bEnable )
      {
        Info.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED;
      }
      else
      {
        Info.Privilege[0].Attributes = 0;
      }
    
      // Get the LUID.
    
      Result = LookupPrivilegeValue ( NULL,
                                      SE_LOCK_MEMORY_NAME,
                                      &(Info.Privilege[0].Luid));
    
      if( Result != TRUE )
      {
        _tprintf( _T("Cannot get privilege for %s.\n"), SE_LOCK_MEMORY_NAME );
        return FALSE;
      }
    
      // Adjust the privilege.
    
      Result = AdjustTokenPrivileges ( Token, FALSE,
                                       (PTOKEN_PRIVILEGES) &Info,
                                       0, NULL, NULL);
    
      // Check the result.
    
      if( Result != TRUE )
      {
        _tprintf (_T("Cannot adjust token privileges (%u)\n"), GetLastError() );
        return FALSE;
      }
      else
      {
        if( GetLastError() != ERROR_SUCCESS )
        {
          _tprintf (_T("Cannot enable the SE_LOCK_MEMORY_NAME privilege; "));
          _tprintf (_T("please check the local policy.\n"));
          return FALSE;
        }
      }
    
      CloseHandle( Token );
    
      return TRUE;
    }
    
    /*
     --->(tail)         (head) ----- ~~~~>
          v              v
    +-------------------------------|-------------------------------+
    |                        virtual memory                         |
    +-------------------------------|-------------------------------+
    <--- Memory Requested Size ---->
    */
    BOOL CyclicMapUserPhysicalPages(void* VirtualAddress, void* VirtualHeadAddress, ULONG_PTR NumberOfPages, ULONG_PTR* PageArray, DWORD dwPageSize){
      ULONG_PTR iStartPage = (ULONG_PTR(VirtualHeadAddress)-ULONG_PTR(VirtualAddress))/dwPageSize;
    
      void* pEnd = ((BYTE*)VirtualAddress)+dwPageSize*iStartPage;
      void* pStart = ((BYTE*)VirtualAddress)+dwPageSize*NumberOfPages;
      BOOL bResult = MapUserPhysicalPages( pEnd, NumberOfPages-iStartPage, PageArray ? (PageArray+iStartPage) : NULL );
      if( !bResult )
        return FALSE;
    
      if (iStartPage)
      {
        bResult = MapUserPhysicalPages( pStart, iStartPage, PageArray );
        if( !bResult ){
          if (PageArray)
            MapUserPhysicalPages( pEnd, NumberOfPages-iStartPage, NULL );
          return FALSE;
        }
      }
      return TRUE;
    }

0

你可以使用地址窗口扩展(AWE)来实现。例如,你可以保留两个虚拟内存区域,然后将它们映射到同一个物理区域的后面。或者你可以保留单个虚拟区域,但映射不同的段。

虽然不像Linux mremap那样方便,但它确实可行。

请查看地址窗口扩展(AWE): https://msdn.microsoft.com/zh-cn/library/windows/desktop/aa366531(v=vs.85).aspx

这是基于MSDN示例的工作代码,供你参考。在运行之前,请记得在Windows中为你的账户获取“锁定页面内存”的特权。

#include <windows.h>
#include <stdio.h>
#include <tchar.h>

#define MEMORY_REQUESTED 1024*1024 // request a megabyte

BOOL
LoggedSetLockPagesPrivilege(HANDLE hProcess,
    BOOL bEnable);

void _cdecl main()
{
    BOOL bResult;                   // generic Boolean value
    ULONG_PTR NumberOfPages;        // number of pages to request
    ULONG_PTR NumberOfPagesInitial; // initial number of pages requested
    ULONG_PTR *aPFNs;               // page info; holds opaque data
    PVOID lpMemReserved;            // AWE window
    PVOID lpMemReserved2;            // AWE window
    SYSTEM_INFO sSysInfo;           // useful system information
    int PFNArraySize;               // memory to request for PFN array

    GetSystemInfo(&sSysInfo);  // fill the system information structure

    _tprintf(_T("This computer has page size %d.\n"), sSysInfo.dwPageSize);

    // Calculate the number of pages of memory to request.

    NumberOfPages = MEMORY_REQUESTED / sSysInfo.dwPageSize;
    _tprintf(_T("Requesting %d pages of memory.\n"), NumberOfPages);

    // Calculate the size of the user PFN array.

    PFNArraySize = NumberOfPages * sizeof(ULONG_PTR);

    _tprintf(_T("Requesting a PFN array of %d bytes.\n"), PFNArraySize);

    aPFNs = (ULONG_PTR *)HeapAlloc(GetProcessHeap(), 0, PFNArraySize);

    if (aPFNs == NULL)
    {
        _tprintf(_T("Failed to allocate on heap.\n"));
        return;
    }

    // Enable the privilege.

    if (!LoggedSetLockPagesPrivilege(GetCurrentProcess(), TRUE))
    {
        return;
    }

    // Allocate the physical memory.

    NumberOfPagesInitial = NumberOfPages;
    bResult = AllocateUserPhysicalPages(GetCurrentProcess(),
        &NumberOfPages,
        aPFNs);

    if (bResult != TRUE)
    {
        _tprintf(_T("Cannot allocate physical pages (%u)\n"), GetLastError());
        return;
    }

    if (NumberOfPagesInitial != NumberOfPages)
    {
        _tprintf(_T("Allocated only %p pages.\n"), (void*)NumberOfPages);
        return;
    }

    // Reserve the virtual memory.

    lpMemReserved = VirtualAlloc(NULL,
        MEMORY_REQUESTED,
        MEM_RESERVE | MEM_PHYSICAL,
        PAGE_READWRITE);

    if (lpMemReserved == NULL)
    {
        _tprintf(_T("Cannot reserve memory.\n"));
        return;
    }

    lpMemReserved2 = VirtualAlloc(NULL,
        MEMORY_REQUESTED,
        MEM_RESERVE | MEM_PHYSICAL,
        PAGE_READWRITE);

    if (lpMemReserved2 == NULL)
    {
        _tprintf(_T("Cannot reserve memory.\n"));
        return;
    }

    // Map the physical memory into the window.

    bResult = MapUserPhysicalPages(lpMemReserved,
        NumberOfPages,
        aPFNs);

    if (bResult != TRUE)
    {
        _tprintf(_T("MapUserPhysicalPages failed (%u)\n"), GetLastError());
        return;
    }
    else {
        int* pa = (int*)lpMemReserved;
        pa[1] = pa[100] = 0xF0F0;
        _tprintf(_T("MapUserPhysicalPages successfully at %p\n"), lpMemReserved);   
    }


    // unmap

    bResult = MapUserPhysicalPages(lpMemReserved,
        NumberOfPages,
        NULL);

    if (bResult != TRUE)
    {
        _tprintf(_T("MapUserPhysicalPages failed (%u)\n"), GetLastError());
        return;
    }

    //remap
    bResult = MapUserPhysicalPages(lpMemReserved2,
        NumberOfPages,
        aPFNs);

    if (bResult != TRUE)
    {
        _tprintf(_T("Re-MapUserPhysicalPages failed (%u)\n"), GetLastError());
        return;
    }
    else {
        int* pa = (int*)lpMemReserved2;
        if(pa[1] != pa[100] || pa[100] != 0xF0F0)
            _tprintf(_T("Re-MapUserPhysicalPages failed (%u)\n"), GetLastError());
        _tprintf(_T("Re-MapUserPhysicalPages successfully at %p\n"), lpMemReserved2);
    }

    // Free the physical pages.

    bResult = FreeUserPhysicalPages(GetCurrentProcess(),
        &NumberOfPages,
        aPFNs);

    if (bResult != TRUE)
    {
        _tprintf(_T("Cannot free physical pages, error %u.\n"), GetLastError());
        return;
    }

    // Free virtual memory.

    bResult = VirtualFree(lpMemReserved,
        0,
        MEM_RELEASE);

    // Release the aPFNs array.

    bResult = HeapFree(GetProcessHeap(), 0, aPFNs);

    if (bResult != TRUE)
    {
        _tprintf(_T("Call to HeapFree has failed (%u)\n"), GetLastError());
    }

    _tprintf(_T("Successfully finished\n"));

}


/*****************************************************************
LoggedSetLockPagesPrivilege: a function to obtain or
release the privilege of locking physical pages.

Inputs:

HANDLE hProcess: Handle for the process for which the
privilege is needed

BOOL bEnable: Enable (TRUE) or disable?

Return value: TRUE indicates success, FALSE failure.

*****************************************************************/
BOOL
LoggedSetLockPagesPrivilege(HANDLE hProcess,
    BOOL bEnable)
{
    struct {
        DWORD Count;
        LUID_AND_ATTRIBUTES Privilege[1];
    } Info;

    HANDLE Token;
    BOOL Result;

    // Open the token.

    Result = OpenProcessToken(hProcess,
        TOKEN_ADJUST_PRIVILEGES,
        &Token);

    if (Result != TRUE)
    {
        _tprintf(_T("Cannot open process token.\n"));
        return FALSE;
    }

    // Enable or disable?

    Info.Count = 1;
    if (bEnable)
    {
        Info.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED;
    }
    else
    {
        Info.Privilege[0].Attributes = 0;
    }

    // Get the LUID.

    Result = LookupPrivilegeValue(NULL,
        SE_LOCK_MEMORY_NAME,
        &(Info.Privilege[0].Luid));

    if (Result != TRUE)
    {
        _tprintf(_T("Cannot get privilege for %s.\n"), SE_LOCK_MEMORY_NAME);
        return FALSE;
    }

    // Adjust the privilege.

    Result = AdjustTokenPrivileges(Token, FALSE,
        (PTOKEN_PRIVILEGES)&Info,
        0, NULL, NULL);

    // Check the result.

    if (Result != TRUE)
    {
        _tprintf(_T("Cannot adjust token privileges (%u)\n"), GetLastError());
        return FALSE;
    }
    else
    {
        if (GetLastError() != ERROR_SUCCESS)
        {
            _tprintf(_T("Cannot enable the SE_LOCK_MEMORY_NAME privilege; "));
            _tprintf(_T("please check the local policy.\n"));
            return FALSE;
        }
    }

    CloseHandle(Token);

    return TRUE;
}

我会将此内容标记为反对,因为您似乎声称多个虚拟页面可以映射到同一物理页面。这不是事实,并且很可能会在您的面前炸掉(即使您成功了)。请参见AllocateUserPhysicalPages - awdz9nld

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接