VirtualAlloc2 函数 (memoryapi.h)

保留、提交或更改指定进程的虚拟地址空间中内存区域的状态(分配的内存初始化为零)。

语法

PVOID VirtualAlloc2(
  [in, optional]      HANDLE                 Process,
  [in, optional]      PVOID                  BaseAddress,
  [in]                SIZE_T                 Size,
  [in]                ULONG                  AllocationType,
  [in]                ULONG                  PageProtection,
  [in, out, optional] MEM_EXTENDED_PARAMETER *ExtendedParameters,
  [in]                ULONG                  ParameterCount
);

参数

[in, optional] Process

进程的句柄。 该函数在进程的虚拟地址空间中分配内存。

句柄必须具有 PROCESS_VM_OPERATION 访问权限。 有关详细信息,请参阅 进程安全性和访问权限

如果 进程NULL,则该函数将为调用进程分配内存。

[in, optional] BaseAddress

为要分配的页面区域指定所需起始地址的指针。

如果 BaseAddressNULL,则该函数将确定分配区域的位置。

如果 BaseAddressNULL,则提供的任何 MEM_ADDRESS_REQUIREMENTS 结构都必须包含所有零,并且基址必须是系统分配粒度的倍数。 若要确定分配粒度,请使用 GetSystemInfo 函数。

如果此地址位于未通过调用 InitializeEnclave初始化的 enclave 中,VirtualAlloc2 为该地址的 enclave 分配零页。 该页必须以前未提交,并且不会使用 Intel Software Guard Extensions 编程模型的 EEXTEND 指令进行测量。

如果初始化的 enclave 中的地址,则分配操作将失败,并出现 ERROR_INVALID_ADDRESS 错误。 对于不支持动态内存管理的 enclave(即 SGX1),这是事实。 SGX2 enclave 将允许分配,并且该页必须在分配后由 enclave 接受。

[in] Size

要分配的内存区域的大小(以字节为单位)。

大小必须始终为页面大小的倍数。

如果 BaseAddressNULL,则该函数会将包含一个或多个字节的所有页面分配在从 BaseAddressBaseAddress+size。 例如,这意味着跨页边界的 2 字节范围会导致函数分配这两个页面。

[in] AllocationType

内存分配的类型。 此参数必须包含以下值之一。

价值 意义
MEM_COMMIT
0x00001000
为指定的保留内存页分配内存费用(从内存的总体大小和磁盘上的分页文件)。 该函数还保证当调用方稍后最初访问内存时,内容将为零。 除非实际访问虚拟地址/直到实际访问虚拟地址,否则不会分配实际物理页。

若要在一个步骤中保留和提交页面,请使用 MEM_COMMIT | MEM_RESERVE调用 VirtualAlloc2

除非已保留整个范围,否则尝试通过指定不带 MEM_RESERVEMEM_COMMITNULLBaseAddress 来尝试提交特定地址范围。 生成的错误代码 ERROR_INVALID_ADDRESS

尝试提交已提交的页面不会导致函数失败。 这意味着可以提交页面,而无需首先确定每个页面的当前承诺状态。

如果 baseAddress 指定 enclave 中的地址,则必须 AllocationType MEM_COMMIT

MEM_RESERVE
0x00002000
保留进程的虚拟地址空间范围,而无需在内存或磁盘上的分页文件中分配任何实际物理存储。

通过使用 MEM_COMMIT再次调用 VirtualAlloc2 来提交保留页。 若要在一个步骤中保留和提交页面,请使用 MEM_COMMIT | MEM_RESERVE调用 VirtualAlloc2

其他内存分配函数(如 mallocLocalAlloc)在释放之前无法使用保留内存。

MEM_REPLACE_PLACEHOLDER
0x00004000
将占位符替换为正常的专用分配。 仅支持数据/pf 支持的分区视图(无图像、物理内存等)。 替换占位符时,BaseAddressSize 必须与占位符的占位符完全匹配,并且提供的任何 MEM_ADDRESS_REQUIREMENTS 结构都必须包含所有零。

将占位符替换为专用分配后,若要将该分配释放回占位符,请参阅 VirtualFreedwFreeType 参数,并 VirtualFreeEx

占位符是保留内存区域的类型。

MEM_RESERVE_PLACEHOLDER
0x00040000
若要创建占位符,请调用 VirtualAlloc2,并将 MEM_RESERVE | MEM_RESERVE_PLACEHOLDERPageProtection 设置为 PAGE_NOACCESS。 若要释放/拆分/合并占位符,请参阅 VirtualFreedwFreeType 参数,VirtualFreeEx

占位符是保留内存区域的类型。

MEM_RESET
0x00080000
指示 BaseAddress大小 指定的内存范围中的数据不再感兴趣。 不应从分页文件读取或写入页面。 但是,内存块稍后将再次使用,因此不应将其取消提交。 此值不能用于任何其他值。

使用此值不能保证使用 MEM_RESET 操作的范围将包含零。 如果希望该区域包含零,请取消提交内存,然后重新提交内存。

使用 MEM_RESET时,VirtualAlloc2 函数将忽略 fProtect的值。 但是,仍必须将 fProtect 设置为有效的保护值,例如 PAGE_NOACCESS

VirtualAlloc2 如果使用 MEM_RESET 并且内存范围映射到文件,则返回错误。 仅当共享视图映射到分页文件时,才可接受。

MEM_RESET_UNDO
0x1000000
MEM_RESET_UNDO 只应在之前成功应用 MEM_RESET 的地址范围上调用。 它表示,BaseAddress 指定的指定内存范围中的数据和 大小 对调用方感兴趣,并尝试扭转 MEM_RESET的影响。 如果函数成功,则表示指定地址范围中的所有数据都保持不变。 如果函数失败,则地址范围中的至少一些数据已替换为零。

此值不能用于任何其他值。 如果在之前未 MEM_RESET 的地址范围上调用 MEM_RESET_UNDO,则行为是未定义的。 指定 MEM_RESET时,VirtualAlloc2 函数将忽略 pageProtection的值。 但是,仍必须将 pageProtection 设置为有效的保护值,例如 PAGE_NOACCESS

Windows Server 2008 R2、Windows 7、Windows Server 2008、Windows Vista、Windows Server 2003 和 Windows XP:在 Windows 8 和 Windows Server 2012 之前不支持 MEM_RESET_UNDO 标志。

 

此参数还可以按指示指定以下值。

价值 意义
MEM_LARGE_PAGES
0x20000000
使用 大型页面支持分配内存。

大小和对齐方式必须是大页最小值的倍数。 若要获取此值,请使用 GetLargePageMinimum 函数。

如果指定此值,还必须指定 MEM_RESERVEMEM_COMMIT

MEM_64K_PAGES
0x20400000
如果可能,向操作系统提示使用 64K 页映射内存。

64K 页是一个内存区域,大小为 64K,几乎和物理连续,几乎和物理对齐在 64K 边界上。

默认情况下,使用MEM_64K_PAGES分配的内存是可分页的,支持内存的物理页是按需分配的(访问时)。 如果物理内存过于碎片,无法组装物理连续的 64K 页,则MEM_64K_PAGES分配的全部或部分都可以改用非连续的小页进行映射。

如果MEM_64K_PAGES与 MEM_EXTENDED_PARAMETER_NONPAGED 属性相结合,将使用非分页的 64K 页映射分配。 在这种情况下,如果无法获取连续的 64K 页,则分配将失败。

如果指定了MEM_64K_PAGES,则 Size 和 BaseAddress 参数必须为 64K 的倍数(BaseAddress 可能为 NULL)。

MEM_PHYSICAL
0x00400000
保留可用于映射 地址窗口扩展插件(AWE)页的地址范围。

此值必须与 MEM_RESERVE 一起使用,并且不能用于其他值。

MEM_TOP_DOWN
0x00100000
以最高可能地址分配内存。 这比常规分配要慢,尤其是在分配很多时。

[in] PageProtection

要分配的页面区域的内存保护。 如果要提交页面,则可以指定内存保护常量之一。

如果 baseAddress 指定 enclave 中的地址,则 PageProtection 不能为以下任何值:

  • PAGE_NOACCESS
  • PAGE_GUARD
  • PAGE_NOCACHE
  • PAGE_WRITECOMBINE

为 enclave 分配动态内存时,PageProtection 参数必须 PAGE_READWRITEPAGE_EXECUTE_READWRITE

[in, out, optional] ExtendedParameters

指向 MEM_EXTENDED_PARAMETER类型的一个或多个扩展参数的可选指针。 每个扩展参数值本身都可以具有 Type 字段,MemExtendedParameterAddressRequirementsMemExtendedParameterNumaNode。 如果未提供 MemExtendedParameterNumaNode 扩展参数,则行为与 VirtualAlloc/MapViewOfFile 函数(即,物理页的首选 NUMA 节点)是根据首次访问内存的线程的理想处理器确定的。

[in] ParameterCount

ExtendedParameters指向的扩展参数数。

返回值

如果函数成功,则返回值是页面分配区域的基址。

如果函数失败,则返回值 NULL。 若要获取扩展的错误信息,请调用 GetLastError

言论

使用此函数可以指定:

  • 新分配的一系列虚拟地址空间和 2 元对齐限制
  • 任意数量的扩展参数
  • 作为扩展参数的物理内存的首选 NUMA 节点(请参阅 ExtendedParameters 参数)
  • 占位符操作(具体而言,替换)。

此 API 提供用于管理虚拟内存以支持高性能游戏和服务器应用程序的专用技术。 例如,占位符允许显式分区、覆盖和重新映射保留内存范围;这可用于实现任意扩展区域或虚拟内存环缓冲区。 VirtualAlloc2 还允许分配具有特定内存对齐方式的内存。

每个页面都有一个关联的 页面状态。 virtualAlloc2 函数 可以执行以下操作:

  • 提交保留页的区域
  • 保留免费页面的区域
  • 同时保留并提交一个免费页面区域

VirtualAlloc2 可以提交已提交但无法保留已保留的页面。 这意味着你可以提交一系列页面,无论它们是否已提交,并且函数不会失败。 但是,一般情况下,应只指定大多数未提交的页面的最小范围,因为提交已提交的大量页面可能会导致 VirtualAlloc2 调用花费更长的时间。

可以使用 VirtualAlloc2 保留页块,然后对 virtualAlloc2 进行其他调用,以提交保留块中的单个页面。 这样一个进程就可以保留其虚拟地址空间的范围,而无需消耗物理存储,直到需要它。

如果 lpAddress 参数未 NULL,则该函数将使用 lpAddressdwSize 参数来计算要分配的页面区域。 整个页面范围的当前状态必须与 flAllocationType 参数指定的分配类型兼容。 否则,该函数将失败,并且未分配任何页面。 此兼容性要求不排除提交已提交的页面;请参阅前面的列表。

若要执行动态生成的代码,请使用 VirtualAlloc2 分配内存,并使用 VirtualProtectEx 函数授予 PAGE_EXECUTE 访问权限。

VirtualAlloc2 函数可用于在指定进程的虚拟地址空间中保留内存的 地址窗口扩展(AWE)区域。 然后,可以使用此内存区域根据应用程序的要求将物理页映射到虚拟内存中和传出虚拟内存。 必须在 AllocationType 参数中设置 MEM_PHYSICALMEM_RESERVE 值。 不能设置 MEM_COMMIT 值。 页面保护必须设置为 PAGE_READWRITE

VirtualFreeEx 函数可以取消提交页面、释放页面的存储,也可以同时取消提交和释放已提交的页面。 它还可以释放保留页,使其成为免费页面。

创建可执行的区域时,调用程序负责确保在代码设置到位后通过适当的调用 FlushInstructionCache 缓存一致性。 否则,尝试从新可执行区域执行代码可能会产生不可预知的结果。

例子

方案 1. 通过映射同一共享内存部分的两个相邻视图来创建循环缓冲区。

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

//
// This function creates a ring buffer by allocating a pagefile-backed section
// and mapping two views of that section next to each other. This way if the
// last record in the buffer wraps it can still be accessed in a linear fashion
// using its base VA.
//

void*
CreateRingBuffer (
    unsigned int bufferSize,
    _Outptr_ void** secondaryView
    )
{
    BOOL result;
    HANDLE section = nullptr;
    SYSTEM_INFO sysInfo;
    void* ringBuffer = nullptr;
    void* placeholder1 = nullptr;
    void* placeholder2 = nullptr;
    void* view1 = nullptr;
    void* view2 = nullptr;

    GetSystemInfo (&sysInfo);

    if ((bufferSize % sysInfo.dwAllocationGranularity) != 0) {
        return nullptr;
    }

    //
    // Reserve a placeholder region where the buffer will be mapped.
    //

    placeholder1 = (PCHAR) VirtualAlloc2 (
        nullptr,
        nullptr,
        2 * bufferSize,
        MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
        PAGE_NOACCESS,
        nullptr, 0
    );

    if (placeholder1 == nullptr) {
        printf ("VirtualAlloc2 failed, error %#x\n", GetLastError());
        goto Exit;
    }

    //
    // Split the placeholder region into two regions of equal size.
    //

    result = VirtualFree (
        placeholder1,
        bufferSize,
        MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER
    );

    if (result == FALSE) {
        printf ("VirtualFreeEx failed, error %#x\n", GetLastError());
        goto Exit;
    }

    placeholder2 = (void*) ((ULONG_PTR) placeholder1 + bufferSize);

    //
    // Create a pagefile-backed section for the buffer.
    //

    section = CreateFileMapping (
        INVALID_HANDLE_VALUE,
        nullptr,
        PAGE_READWRITE,
        0,
        bufferSize, nullptr
    );

    if (section == nullptr) {
        printf ("CreateFileMapping failed, error %#x\n", GetLastError());
        goto Exit;
    }

    //
    // Map the section into the first placeholder region.
    //

    view1 = MapViewOfFile3 (
        section,
        nullptr,
        placeholder1,
        0,
        bufferSize,
        MEM_REPLACE_PLACEHOLDER,
        PAGE_READWRITE,
        nullptr, 0
    );

    if (view1 == nullptr) {
        printf ("MapViewOfFile3 failed, error %#x\n", GetLastError());
        goto Exit;
    }

    //
    // Ownership transferred, don't free this now.
    //

    placeholder1 = nullptr;

    //
    // Map the section into the second placeholder region.
    //

    view2 = MapViewOfFile3 (
        section,
        nullptr,
        placeholder2,
        0,
        bufferSize,
        MEM_REPLACE_PLACEHOLDER,
        PAGE_READWRITE,
        nullptr, 0
    );

    if (view2 == nullptr) {
        printf ("MapViewOfFile3 failed, error %#x\n", GetLastError());
        goto Exit;
    }

    //
    // Success, return both mapped views to the caller.
    //

    ringBuffer = view1;
    *secondaryView = view2;

    placeholder2 = nullptr;
    view1 = nullptr;
    view2 = nullptr;

Exit:

    if (section != nullptr) {
        CloseHandle (section);
    }

    if (placeholder1 != nullptr) {
        VirtualFree (placeholder1, 0, MEM_RELEASE);
    }

    if (placeholder2 != nullptr) {
        VirtualFree (placeholder2, 0, MEM_RELEASE);
    }

    if (view1 != nullptr) {
        UnmapViewOfFileEx (view1, 0);
    }

    if (view2 != nullptr) {
        UnmapViewOfFileEx (view2, 0);
    }

    return ringBuffer;
}

int __cdecl wmain()
{
    char* ringBuffer;
    void* secondaryView;
    unsigned int bufferSize = 0x10000;

    ringBuffer = (char*) CreateRingBuffer (bufferSize, &secondaryView);

    if (ringBuffer == nullptr) {
        printf ("CreateRingBuffer failed\n");
        return 0;
    }

    //
    // Make sure the buffer wraps properly.
    //

    ringBuffer[0] = 'a';

    if (ringBuffer[bufferSize] == 'a') {
        printf ("The buffer wraps as expected\n");
    }

    UnmapViewOfFile (ringBuffer);
    UnmapViewOfFile (secondaryView);
}

方案 2. 分配内存时指定首选 NUMA 节点。


void*
AllocateWithPreferredNode (size_t size, unsigned int numaNode)
{
    MEM_EXTENDED_PARAMETER param = {0};

    param.Type = MemExtendedParameterNumaNode;
    param.ULong = numaNode;

    return VirtualAlloc2 (
        nullptr, nullptr,
        size,
        MEM_RESERVE | MEM_COMMIT,
        PAGE_READWRITE,
        &param, 1);
}

方案 3. 在特定虚拟地址范围(此示例中低于 4GB)和特定对齐方式分配内存。


void*
AllocateAlignedBelow2GB (size_t size, size_t alignment)
{
    MEM_ADDRESS_REQUIREMENTS addressReqs = {0};
    MEM_EXTENDED_PARAMETER param = {0};

    addressReqs.Alignment = alignment;
    addressReqs.HighestEndingAddress = (PVOID)(ULONG_PTR) 0x7fffffff;

    param.Type = MemExtendedParameterAddressRequirements;
    param.Pointer = &addressReqs;

    return VirtualAlloc2 (
        nullptr, nullptr,
        size,
        MEM_RESERVE | MEM_COMMIT,
        PAGE_READWRITE,
        &param, 1);
}

要求

要求 价值
最低支持的客户端 Windows 10 [仅限桌面应用]
支持的最低服务器 Windows Server 2016 [仅限桌面应用]
目标平台 窗户
标头 memoryapi.h (包括 Windows.h)
onecore.lib
DLL Kernel32.dll

另请参阅

内存管理功能

ReadProcessMemory

虚拟内存函数

VirtualAllocExNuma

VirtualFreeEx

VirtualLock

VirtualProtect

VirtualQuery

WriteProcessMemory