指定されたプロセスの仮想アドレス空間内のメモリ領域の状態を予約、コミット、または変更します (割り当てられたメモリは 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 を呼び出して初期化していないエンクレーブ内にある場合、 VirtualAlloc2 は、そのアドレスのエンクレーブに 0 のページを割り当てます。 このページは以前はコミットされていない必要があり、Intel Software Guard Extensions プログラミング モデルの EEXTEND 命令では測定されません。
アドレスが初期化したエンクレーブ内にある場合、割り当て操作は ERROR_INVALID_ADDRESS エラーで失敗します。 これは、動的メモリ管理 (つまり SGX1) をサポートしていないエンクレーブに当てはまります。 SGX2 エンクレーブは割り当てを許可し、ページは割り当てられた後にエンクレーブによって受け入れられる必要があります。
[in] Size
割り当てるメモリ領域のサイズ (バイト単位)。
サイズは常にページ サイズの倍数である必要があります。
BaseAddress が NULL でない場合、関数は BaseAddress から BaseAddress+Size までの範囲内の 1 つ以上のバイトを含むすべてのページを割り当てます。 これは、たとえば、ページ境界をまたぐ 2 バイト範囲によって、関数が両方のページを割り当てることを意味します。
[in] AllocationType
メモリ割り当ての種類。 このパラメーターには、次のいずれかの値が含まれている必要があります。
| 価値 | 意味 |
|---|---|
|
指定された予約済みメモリ ページのメモリ使用量 (メモリの全体的なサイズとディスク上のページング ファイルから) を割り当てます。 また、この関数は、呼び出し元が最初にメモリにアクセスするときに、内容がゼロになることも保証します。 仮想アドレスが実際にアクセスされるまで、実際の物理ページは割り当てされません。
1 つの手順でページを予約してコミットするには、 MEM_RESERVEを指定せずにMEM_COMMITを指定して特定のアドレス範囲をコミットしようとすると、範囲全体が既に予約されていない限り、NULL 以外の BaseAddress は失敗します。 結果のエラー コードは ERROR_INVALID_ADDRESS。 既にコミットされているページをコミットしようとしても、関数は失敗しません。 つまり、各ページの現在のコミットメント状態を最初に判断することなく、ページをコミットできます。 BaseAddress がエンクレーブ内のアドレスを指定する場合は、AllocationType をMEM_COMMITする必要があります。 |
|
メモリまたはディスク上のページング ファイル内の実際の物理ストレージを割り当てずに、プロセスの仮想アドレス空間の範囲を予約します。
予約済みページをコミットするには、MEM_COMMITを使用して VirtualAlloc2 をもう一度呼び出します。 1 つの手順でページを予約してコミットするには、 malloc や LocalAlloc などの他のメモリ割り当て関数では、解放されるまで予約済みメモリを使用できません。 |
|
プレースホルダーを通常のプライベート割り当てに置き換えます。 サポートされているのは、データ/pf に基づくセクション ビューのみです (イメージや物理メモリなどはサポートされません)。 プレースホルダーを置き換える場合、 BaseAddress と Size はプレースホルダーのものと完全に一致する必要があり、指定された MEM_ADDRESS_REQUIREMENTS 構造体はすべてゼロで構成されている必要があります。
プレースホルダーをプライベート割り当てに置き換えた後、その割り当てをプレースホルダーに解放するには、VirtualFree と VirtualFreeEx の dwFreeType パラメーターを参照してください。 プレースホルダーは、予約済みメモリ領域の一種です。 |
|
プレースホルダーを作成するには、MEM_RESERVE | MEM_RESERVE_PLACEHOLDERと PageProtection を PAGE_NOACCESS に設定して VirtualAlloc2 を呼び出します。 プレースホルダーを解放/分割/結合するには、VirtualFree と VirtualFreeEx の dwFreeType パラメーターを参照してください。
プレースホルダーは、予約済みメモリ領域の一種です。 |
|
BaseAddress と Size で指定されたメモリ範囲内のデータが関心を持たなくなったことを示します。 ページをページング ファイルから読み取ったり、ページング ファイルに書き込んだりしないでください。 ただし、メモリ ブロックは後で再度使用されるため、デコミットしないでください。 この値は、他の値と共に使用することはできません。
この値を使用しても、 MEM_RESET で操作される範囲にゼロが含まれるという保証はありません。 範囲にゼロを含める場合は、メモリをデコミットしてから、再コミットします。 MEM_RESETを使用すると、VirtualAlloc2 関数は fProtect の値を無視します。 ただし、 fProtect を有効な保護値 ( PAGE_NOACCESS など) に設定する必要があります。 MEM_RESETを使用し、メモリの範囲がファイルにマップされている場合、VirtualAlloc2 はエラーを返します。 共有ビューは、ページング ファイルにマップされている場合にのみ使用できます。 |
|
MEM_RESET_UNDO は、MEM_RESETが以前に正常に 適用されたアドレス 範囲でのみ呼び出す必要があります。
これは、BaseAddress と Size で指定されたメモリ範囲内のデータが呼び出し元にとって重要であり、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: MEM_RESET_UNDO フラグは、Windows 8 および Windows Server 2012 までサポートされていません。 |
このパラメーターでは、次の値を指定することもできます。
| 価値 | 意味 |
|---|---|
|
大きなページのサポートを使用してメモリを割り当てます。
サイズと配置は、大きなページの最小値の倍数である必要があります。 この値を取得するには、 GetLargePageMinimum 関数を使用します。 この値を指定する場合は、 MEM_RESERVE と MEM_COMMITも指定する必要があります。 |
|
可能であれば、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 である可能性があります)。 |
|
アドレス ウィンドウ拡張 (AWE) ページのマップに使用できるアドレス範囲を予約します。
この値は 、MEM_RESERVE で使用する必要があり、他の値は使用できません。 |
|
可能な限り高いアドレスでメモリを割り当てます。 これは、特に割り当てが多い場合に、通常の割り当てよりも遅くなる可能性があります。 |
[in] PageProtection
割り当てるページの領域のメモリ保護。 ページがコミットされている場合は、 メモリ保護定数のいずれかを指定できます。
BaseAddress がエンクレーブ内のアドレスを指定する場合、PageProtection は次のいずれかの値にすることはできません。
- PAGE_NOACCESS
- PAGE_GUARD
- PAGE_NOCACHE
- PAGE_WRITECOMBINE
エンクレーブに動的メモリを割り当てるときは、 PageProtection パラメーターを PAGE_READWRITE または PAGE_EXECUTE_READWRITEする必要があります。
[in, out, optional] ExtendedParameters
MEM_EXTENDED_PARAMETER型の 1 つ以上の拡張パラメーターへの省略可能なポインター。 これらの拡張パラメーター値はそれぞれ、MemExtendedParameterAddressRequirements または MemExtendedParameterNumaNode のいずれかの Type フィールドを持つことができます。 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) 領域を予約できます。 その後、このメモリ領域を使用して、アプリケーションの必要に応じて、仮想メモリとの間で物理ページをマップできます。 AllocationType パラメーターでは、MEM_PHYSICAL値とMEM_RESERVE値を設定する必要があります。 MEM_COMMIT値は設定しないでください。 ページ保護は PAGE_READWRITEに設定する必要があります。
VirtualFreeEx 関数は、コミットされたページのコミットを解除したり、ページのストレージを解放したり、コミットされたページを同時にコミットおよび解放したりできます。 また、予約ページを解放して、無料のページにすることもできます。
実行可能なリージョンを作成する場合、呼び出し元のプログラムは、コードが設定されたら 、FlushInstructionCache への適切な呼び出しを介してキャッシュの一貫性を確保する責任を負います。 そうしないと、新しく実行可能なリージョンからコードを実行しようとすると、予期しない結果が発生する可能性があります。
例
シナリオ 1. 同じ共有メモリ セクションの 2 つの隣接するビューをマッピングして、循環バッファーを作成します。
#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,
¶m, 1);
}
シナリオ 3. 特定の仮想アドレス範囲 (この例では 4 GB 未満) にメモリを割り当て、特定のアラインメントを使用します。
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,
¶m, 1);
}
必要条件
| 要件 | 価値 |
|---|---|
| サポートされる最小クライアント | Windows 10 [デスクトップ アプリのみ] |
| サポートされる最小サーバー | Windows Server 2016 [デスクトップ アプリのみ] |
| ターゲット プラットフォーム の | ウィンドウズ |
| Header | memoryapi.h (Windows.h を含む) |
| Library | onecore.lib |
| DLL | Kernel32.dll |