VirtualAlloc2 함수(memoryapi.h)

지정된 프로세스의 가상 주소 공간 내에서 메모리 영역의 상태를 예약, 커밋 또는 변경합니다(할당된 메모리는 0으로 초기화됨).

통사론

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 액세스 권한이 있어야 합니다. 자세한 내용은 프로세스 보안 및 액세스 권한을 참조하세요.

Process가 NULL이면 함수는 호출 프로세스에 대한 메모리를 할당합니다.

[in, optional] BaseAddress

할당하려는 페이지 영역에 대해 원하는 시작 주소를 지정하는 포인터입니다.

BaseAddress가 NULL인 경우 함수는 지역을 할당할 위치를 결정합니다.

BaseAddress가 NULL이 아닌 경우 제공된 MEM_ADDRESS_REQUIREMENTS 구조체는 모두 0으로 구성되어야 하며 기본 주소는 시스템 할당 세분성의 배수여야 합니다. 할당 세분성을 확인하려면 GetSystemInfo 함수를 사용합니다.

이 주소가 InitializeEnclave를 호출하여 초기화하지 않은 enclave 내에 있는 경우 VirtualAlloc2 는 해당 주소의 enclave에 대해 0 페이지를 할당합니다. 이 페이지는 이전에 커밋되지 않았어야 하며 Intel Software Guard 확장 프로그래밍 모델의 EEXTEND 명령으로 측정되지 않습니다.

주소가 초기화한 enclave 내에 있으면 할당 작업이 실패하고 ERROR_INVALID_ADDRESS 오류가 발생합니다. 동적 메모리 관리(예: SGX1)를 지원하지 않는 enclave의 경우 마찬가지입니다. SGX2 enclave는 할당을 허용하며, 할당된 후 Enclave에서 페이지를 수락해야 합니다.

[in] Size

할당할 메모리 영역의 크기(바이트)입니다.

크기는 항상 페이지 크기의 배수여야 합니다.

BaseAddress가 NULL이 아닌 경우 함수는 BaseAddress에서 BaseAddress+ 크기까지 범위에서 하나 이상의 바이트를 포함하는 모든 페이지를 할당합니다. 예를 들어 페이지 경계를 가로지르는 2 바이트 범위는 함수가 두 페이지를 모두 할당하도록 합니다.

[in] AllocationType

메모리 할당의 유형입니다. 이 매개 변수는 다음 값 중 하나를 포함해야 합니다.

값 의미
MEM_COMMIT
0x00001000
지정된 예약된 메모리 페이지에 대한 메모리 요금(전체 메모리 크기 및 디스크의 페이징 파일)을 할당합니다. 또한 이 함수는 나중에 호출자가 처음에 메모리에 액세스할 때 콘텐츠가 0이 되도록 보장합니다. 실제 실제 페이지는 가상 주소에 실제로 액세스하지 않는 한 할당되지 않습니다.

한 단계에서 페이지를 예약하고 커밋하려면 다음을 사용하여 VirtualAlloc2 를 MEM_COMMIT | MEM_RESERVE호출합니다.

MEM_RESERVE 없이 MEM_COMMIT 지정하여 특정 주소 범위를 커밋하려고 시도하면 전체 범위 가 이미 예약되지 않은 경우 NULL이 아닌 BaseAddress가 실패합니다. 결과 오류 코드가 ERROR_INVALID_ADDRESS.

이미 커밋된 페이지를 커밋하려고 하면 함수가 실패하지 않습니다. 즉, 각 페이지의 현재 약정 상태를 먼저 결정하지 않고도 페이지를 커밋할 수 있습니다.

BaseAddress가 enclave 내에서 주소를 지정하는 경우 AllocationType은 MEM_COMMIT 합니다.

MEM_RESERVE
0x00002000
메모리 또는 디스크의 페이징 파일에 실제 실제 스토리지를 할당하지 않고 프로세스의 가상 주소 공간 범위를 예약합니다.

MEM_COMMITVirtualAlloc2를 다시 호출하여 예약된 페이지를 커밋합니다. 한 단계에서 페이지를 예약하고 커밋하려면 다음을 사용하여 VirtualAlloc2 를 MEM_COMMIT | MEM_RESERVE호출합니다.

malloc 및 LocalAlloc와 같은 다른 메모리 할당 함수는 릴리스될 때까지 예약된 메모리를 사용할 수 없습니다.

MEM_REPLACE_PLACEHOLDER
0x00004000
자리 표시자를 일반 프라이빗 할당으로 바꿉니다. 데이터/pf 지원 섹션 뷰만 지원됩니다(이미지, 실제 메모리 등은 없음). 자리 표시자를 바꿀 때 BaseAddress 및 Size 는 자리 표시자의 개체 틀과 정확히 일치해야 하며 제공된 모든 MEM_ADDRESS_REQUIREMENTS 구조체는 모두 0으로 구성되어야 합니다.

자리 표시자를 프라이빗 할당으로 바꾼 후 해당 할당을 자리 표시자로 다시 해제하려면 VirtualFree 및 VirtualFreeEx의 dwFreeType 매개 변수를 참조하세요.

자리 표시자는 예약된 메모리 영역의 유형입니다.

MEM_RESERVE_PLACEHOLDER
0x00040000
자리 표시자를 만들려면 VirtualAlloc2 with 및 MEM_RESERVE | MEM_RESERVE_PLACEHOLDERPageProtection 을 호출하여 PAGE_NOACCESS. 자리 표시자를 해제/분할/병합하려면 VirtualFree 및 VirtualFreeEx의 dwFreeType 매개 변수를 참조하세요.

자리 표시자는 예약된 메모리 영역의 유형입니다.

MEM_RESET
0x00080000
BaseAddress 및 Size로 지정된 메모리 범위의 데이터가 더 이상 관심이 없음을 나타냅니다. 페이지를 페이징 파일에서 읽거나 쓸 수 없습니다. 그러나 메모리 블록은 나중에 다시 사용되므로 커밋을 해제해서는 안 됩니다. 이 값은 다른 값과 함께 사용할 수 없습니다.

이 값을 사용하면 MEM_RESET 작동하는 범위에 0이 포함되지 않습니다. 범위에 0이 포함되도록 하려면 메모리를 커밋 해제한 다음 다시 커밋합니다.

MEM_RESET 사용하는 경우 VirtualAlloc2 함수는 fProtect 값을 무시합니다. 그러나 여전히 fProtect 를 PAGE_NOACCESS 같은 유효한 보호 값으로 설정해야 합니다.

MEM_RESET 사용하고 메모리 범위가 파일에 매핑되는 경우 VirtualAlloc2에서 오류를 반환합니다. 공유 뷰는 페이징 파일에 매핑된 경우에만 허용됩니다.

MEM_RESET_UNDO
0x1000000
MEM_RESET_UNDO MEM_RESET 이전에 성공적으로 적용된 주소 범위에서만 호출해야 합니다. BaseAddress 및 Size로 지정된 지정된 메모리 범위의 데이터가 호출자에게 관심이 있음을 나타내며 MEM_RESET 효과를 되돌리려고 시도합니다. 함수가 성공하면 지정된 주소 범위의 모든 데이터가 그대로 유지됩니다. 함수가 실패하면 주소 범위의 일부 데이터가 0으로 바뀌었습니다.

이 값은 다른 값과 함께 사용할 수 없습니다. 이전에 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: MEM_RESET_UNDO 플래그는 Windows 8 및 Windows Server 2012까지 지원되지 않습니다.

이 매개 변수는 다음 값을 표시된 대로 지정할 수도 있습니다.

값 의미
MEM_LARGE_PAGES
0x20000000
큰 페이지 지원을 사용하여 메모리를 할당합니다.

크기 및 맞춤은 큰 페이지 최소값의 배수여야 합니다. 이 값을 가져오려면 GetLargePageMinimum 함수를 사용합니다.

이 값을 지정하는 경우 MEM_RESERVE 및 MEM_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_READWRITE 또는 PAGE_EXECUTE_READWRITE 합니다.

[in, out, optional] ExtendedParameters

MEM_EXTENDED_PARAMETER 형식의 하나 이상의 확장 매개 변수에 대한 선택적 포인터입니다. 이러한 각 확장 매개 변수 값 자체에는 MemExtendedParameterAddressRequirements 또는 MemExtendedParameterNumaNode의 형식 필드가 있을 수 있습니다. MemExtendedParameterNumaNode 확장 매개 변수가 제공되지 않는 경우 동작은 VirtualAlloc/MapViewOfFile 함수와 동일합니다(즉, 실제 페이지에 대한 기본 NUMA 노드는 먼저 메모리에 액세스하는 스레드의 이상적인 프로세서에 따라 결정됨).

[in] ParameterCount

ExtendedParameters가 가리키는 확장 매개 변수의 수입니다.

반환 값

함수가 성공하면 반환 값은 할당된 페이지 영역의 기본 주소입니다.

함수가 실패하면 반환 값은 NULL입니다. 확장 오류 정보를 얻으려면 GetLastError를 호출합니다.

발언

이 함수를 사용하면 다음을 지정할 수 있습니다.

  • 새 할당에 대한 다양한 가상 주소 공간 및 2개 맞춤 제한 기능
  • 임의의 확장 매개 변수 수
  • 물리적 메모리에 대한 기본 NUMA 노드를 확장 매개 변수로 사용합니다( ExtendedParameters 매개 변수 참조).
  • 자리 표시자 작업(특히 대체)입니다.

이 API는 고성능 게임 및 서버 애플리케이션을 지원하기 위해 가상 메모리를 관리하기 위한 특수 기술을 제공합니다. 예를 들어 자리 표시자를 사용하면 예약된 메모리 범위를 명시적으로 분할, 오버레이 및 다시 매핑할 수 있습니다. 임의로 확장 가능한 지역 또는 가상 메모리 링 버퍼를 구현하는 데 사용할 수 있습니다. VirtualAlloc2 를 사용하면 특정 메모리 맞춤을 사용하여 메모리를 할당할 수도 있습니다.

각 페이지에는 연결된 페이지 상태가 있습니다. VirtualAlloc2 함수는 다음 작업을 수행할 수 있습니다.

  • 예약된 페이지의 영역 커밋
  • 무료 페이지 영역 예약
  • 무료 페이지 영역을 동시에 예약 및 커밋

VirtualAlloc2 는 이미 커밋된 페이지를 커밋할 수 있지만 이미 예약된 페이지는 예약할 수 없습니다. 즉, 이미 커밋되었는지 여부에 관계없이 다양한 페이지를 커밋할 수 있으며 함수가 실패하지 않습니다. 그러나 일반적으로 커밋되지 않은 페이지의 최소 범위만 지정해야 합니다. 이미 커밋된 많은 페이지를 커밋하면 VirtualAlloc2 호출이 훨씬 더 오래 걸릴 수 있기 때문입니다.

VirtualAlloc2를 사용하여 페이지 블록을 예약한 다음, VirtualAlloc2를 추가로 호출하여 예약된 블록에서 개별 페이지를 커밋할 수 있습니다. 이렇게 하면 프로세스가 필요할 때까지 실제 스토리지를 사용하지 않고도 해당 가상 주소 공간의 범위를 예약할 수 있습니다.

lpAddress 매개 변수가 NULL이 아닌 경우 함수는 lpAddress 및 dwSize 매개 변수를 사용하여 할당할 페이지 영역을 계산합니다. 전체 페이지 범위의 현재 상태는 flAllocationType 매개 변수로 지정된 할당 형식과 호환되어야 합니다. 그렇지 않으면 함수가 실패하고 페이지가 할당되지 않습니다. 이 호환성 요구 사항은 이미 커밋된 페이지를 커밋하는 것을 배제하지 않습니다. 위의 목록을 참조하세요.

동적으로 생성된 코드를 실행하려면 VirtualAlloc2 를 사용하여 메모리를 할당하고 VirtualProtectEx 함수를 사용하여 PAGE_EXECUTE 액세스 권한을 부여합니다.

VirtualAlloc2 함수를 사용하여 지정된 프로세스의 가상 주소 공간 내에서 AWE(Address Windowing Extensions) 메모리 영역을 예약할 수 있습니다. 그런 다음, 이 메모리 영역을 사용하여 애플리케이션에서 요구하는 대로 가상 메모리 내부 및 외부에 실제 페이지를 매핑할 수 있습니다. MEM_PHYSICAL 및 MEM_RESERVE 값은 AllocationType 매개 변수에서 설정해야 합니다. 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 [데스크톱 앱만 해당]
대상 플랫폼 Windows
Header memoryapi.h(Windows.h 포함)
Library onecore.lib
DLL Kernel32.dll

참고 항목

메모리 관리 함수

ReadProcessMemory

가상 메모리 함수

VirtualAllocExNuma

VirtualFreeEx

VirtualLock

VirtualProtect

VirtualQuery

WriteProcessMemory