前回の「Windowsメモリの深層(第2回) ── 物理ページの一生」では、Working Setから外れた物理ページがModified、Standby、Free、Zeroedをどう移動するかを追いました。
Standbyには、DLL、EXE、マップドファイル、ファイルキャッシュのページも残ります。
ここで疑問が生まれます。
同じkernel32.dllを100プロセスが使うとき、コードページを100組もRAMへ置くのでしょうか。
同じファイルを2プロセスがメモリマップしたとき、片方が読んだページをもう片方も使えるのでしょうか。
答えは、複数の仮想アドレスから同じ物理ページへ対応付ける、です。
その共有単位を表す中心がセクションオブジェクトで、書き込み時だけ共有を分岐させる仕組みがコピーオンライト(CoW)です。
本記事では、EXE・DLL、データファイル、ページファイルバック共有メモリ、ファイルキャッシュが、同じファイルストリーム上でどこに結び付き、どの処理経路が分かれるかを追います。
数字の読み方は導入編「Windowsの『メモリ使用量』は何を表しているのか」を前提にします。
「Windowsメモリの深層」全3回
- 第1回:仮想アドレスとページフォルト
Commit済みの仮想ページが物理RAMを得る瞬間を追います。 - 第2回:物理ページの一生
Working Setから外れたページの状態遷移を追います。 - 第3回(本記事):セクションオブジェクトとコピーオンライト
DLL、ファイルマッピング、共有メモリが物理ページを共有する仕組みを追います。
第3回が答える疑問は、次の1つです。
同じDLLやファイルを、なぜ複数プロセスが1組の物理ページとして使えるのか。
対象読者は、DLLの共有、CreateFileMapping、MapViewOfFile、共有メモリ、CoW、ファイルキャッシュの関係を、APIの使い方だけでなく内部構造から理解したい開発者・運用担当者です。
前提環境はWindows 10/11または現行のWindows Server、前提知識は仮想アドレス、ページフォルト、Working Setの基礎です。
難易度は中級です。Control AreaやPrototype PTEという内部用語も使いますが、観測はVMMap、Process Explorer、QueryWorkingSetExで再現できます。
1. まず結論
最初に全体像をまとめます。
- セクションオブジェクトは、共有可能なメモリ範囲を表します。
各プロセスは、そのセクションの一部を自分の仮想空間へ「ビュー」としてマップします。1 - 同じセクションのビューは、同じ仮想アドレスである必要がありません。
プロセスAの0x000001...とプロセスBの0x000002...が、同じセクションオフセットと同じ物理ページを指せます。2 - ファイルバックとページファイルバックがある。
前者は実ファイル、後者は明示的なファイルを持たない共有メモリなどに使います。2 - EXE/DLLのロードはイメージセクション、通常のファイルマッピングはデータセクションです。
SEC_IMAGEではPE内のセクション属性がページ保護を決めます。3 - 読み取り中は同じ物理ページを共有できる。
片方がCoWページへ書くと、そのページだけコピーして書いたプロセスのPTEを差し替えます。4 - 同じファイルストリーム上でも、キャッシュ・データ・イメージの経路は分かれる。
Cache Managerのcached I/OはSharedCacheMap、データマッピングはDataSectionObject、EXE/DLLはImageSectionObjectを使います。3つはSECTION_OBJECT_POINTERSで同じファイルストリームに結び付くものの、イメージフォルトがCache Managerを通るわけではありません。5 - Private BytesだけではCoW発生を確定できない。
FILE_MAP_COPYは将来全ページが私有化される可能性に備え、ビュー全体へ先にCommitを課すためです。ページ単位の確認にはQueryWorkingSetExのSharedビットを使います。67
一文でまとめるなら、共有するのは仮想アドレスではなく、セクション内の内容と、その時点で対応する物理ページです。
2. セクションオブジェクトとビュー
Microsoftの定義では、セクションオブジェクトは共有できるメモリの区画を表し、ファイルをプロセスのアドレス空間へマップする機構でもあります。1
セクションそのものと、各プロセスから見えるビューを分けて考えます。
| 概念 | 役割 |
|---|---|
| セクションオブジェクト | 共有する内容、サイズ、バックストア、保護の上限を表す |
| ビュー | セクションの一部を、あるプロセスの仮想アドレス範囲へ見せる |
| PTE | ビュー内の各仮想ページを、現在の物理ページや未実体化状態へ結ぶ |
| PFN | 実際にRAMへ存在する物理ページを表す |
Win32では、CreateFileMappingがファイルマッピングオブジェクトのハンドルを返し、MapViewOfFileがプロセスの仮想空間へビューを作ります。3
HANDLE mapping = CreateFileMappingW(
file,
nullptr,
PAGE_READONLY,
0,
0,
nullptr);
void* view = MapViewOfFile(
mapping,
FILE_MAP_READ,
0,
0,
0);
CreateFileMappingだけでは、まだプロセスから読めるアドレスは得られません。
ビューを作っても、全ページが直ちにRAMへ入るわけではありません。最初に触れたページから、ページフォルトでファイル内容を読み込み、PTEへ物理ページを結びます。8
つまり第1回のデマンドページングは、セクションビューにもそのまま適用されます。
2.1. 同じセクション、異なる仮想アドレス
プロセスAとBが同じセクションの同じオフセットをマップしても、ビューの開始アドレスは異なり得ます。
Process A: 0x000001A00000 + 0x3000 ─┐
├─ Section offset 0x3000 ─ Physical page PFN X
Process B: 0x000002700000 + 0x3000 ─┘
共有メモリへ生ポインターを保存してはいけない理由がここにあります。
プロセスAのポインター値は、プロセスBでは無関係なアドレスかもしれません。
共有構造には、ビュー先頭からのオフセット、固定幅整数、明示したバージョンと境界調整を使います。MicrosoftのMapViewOfFileExドキュメントも、同じアドレスが将来も利用できる保証はないため、ポインターではなくベースからのオフセットを保存するよう勧めています。6
3. ファイルバックとページファイルバック
セクションは、内容を復元する場所で大きく2種類に分かれます。
3.1. ファイルバックセクション
実ファイルをCreateFileMappingへ渡します。
- 読み取り専用ビューは、必要なページをファイルから読みます。
- 読み書きビューの変更は、そのファイルのデータとして扱われます。
- CoWビューの変更は元ファイルへ書かれず、私有ページになります。
ファイルバックページがcleanなら、物理ページを捨てても元ファイルから再読込できます。
この性質が、第2回で見たStandbyとファイルキャッシュの効率を支えます。
3.2. ページファイルバックセクション
CreateFileMappingのhFileへINVALID_HANDLE_VALUEを渡し、サイズを指定します。
HANDLE mapping = CreateFileMappingW(
INVALID_HANDLE_VALUE,
nullptr,
PAGE_READWRITE,
0,
64 * 1024,
L"Local\\KomuraMemoryDemo");
明示的なデータファイルを持たず、ページファイルで支えられるセクションです。初期内容はゼロで、名前、ハンドル継承、DuplicateHandleなどを通じて複数プロセスが同じオブジェクトを開けます。93
変更は同じ共有ページをマップするプロセスから見えます。
セクションオブジェクトが破棄されれば、内容を永続ファイルとして残す用途ではありません。2
共有メモリには排他制御が自動で付くわけではありません。mutex、semaphore、event、lock-freeプロトコルなどを別途設計します。9
4. イメージマッピングとデータマッピング
「EXE・DLLもファイルマッピング」と言うとき、通常データファイルとの違いを残しておく必要があります。
| 項目 | イメージマッピング | データマッピング |
|---|---|---|
| 主な用途 | EXE、DLLのロード | 通常ファイル、共有データ |
| 作成属性 | SEC_IMAGE |
PAGE_READONLY、PAGE_READWRITEなど |
| ページ保護 | PEイメージ内の属性が決める | マッピングとビューの指定が決める |
| 書き込み | writable sectionやCoWで私有化し得る | shared writeまたはCoWを選べる |
VirtualQueryのType |
MEM_IMAGE |
MEM_MAPPED |
SEC_IMAGEでは、CreateFileMappingへ渡した通常の保護値より、実行イメージ自身のセクション属性がビューのページ保護を決めます。3
コードのような変更されないページは、多数のプロセスで同じ物理ページを共有できます。
プロセス固有に変更が必要なページだけ、CoWで分岐します。
ASLRによる再配置、ローダーの修正、ホットパッチ、実際のPEセクション属性などにより、すべてのDLLページが必ず共有されるわけではありません。
重要なのは、共有可能なページを先に共有し、変更が必要なページだけ遅延コピーする設計です。
5. コピーオンライトの一部始終
2プロセスが同じCoWページを読んでいる状態から、Process Aだけが1バイト書くまでを追います。
5.1. 書く前
両プロセスのPTEは、概念的には同じ共有ページへ到達します。
Process A PTE ─┐
├─ shared PFN X (read / copy-on-write)
Process B PTE ─┘
読み取りはそのまま成功します。
5.2. 書き込みで保護フォルト
CoWページは、最初から通常の共有書き込み可能ページにはしません。
Process Aが書こうとすると、CPUは保護フォルトを発生させます。
メモリマネージャーは、違法な書き込みではなくCoW属性への書き込みだと判定します。
5.3. 新しい物理ページを作る
Windowsは次を行います。
- Process A用の物理ページを1枚得る。
- PFN Xの内容を新ページPFN Yへコピーする。
- Process AのPTEをPFN Yへ差し替える。
- Process A側の保護を通常の読み書きへ変える。
- 失敗した書き込み命令を再実行する。
Process A PTE ─── private PFN Y (read/write、変更後)
Process B PTE ─── shared PFN X (元の内容)
Process Bは元の内容を読み続け、Process Aの変更を見ません。
これがCopy-on-Writeです。DLLの共有も、FILE_MAP_COPYも、書くまでコピーしないという同じ原理を使います。46
5.4. FILE_MAP_WRITEとの違い
FILE_MAP_WRITEで共有書き込みするページは、片方の変更が同じファイルマッピングを使う別ビューからも見える設計です。
FILE_MAP_COPYは、書いたページだけプロセス固有になり、変更は元ファイルへ戻りません。ビューを外すと変更は失われます。6
「共有メモリで更新を伝えたい」のか、「共通の初期データから各プロセスが私有変更したい」のかで選択が逆になります。
6. CoW後もMEM_MAPPED / MEM_IMAGEのまま
CoW後のページは物理的にはPrivateになりました。
それならVirtualQueryのTypeがMEM_PRIVATEへ変わると思いがちです。
実際には、データビューならMEM_MAPPED、実行イメージならMEM_IMAGEのままです。VirtualQueryは、その領域がどの初期割り当てに由来するかを報告するためです。7
ページ単位でCoW済みかを見るには、次の手順を使います。
- 対象ページへアクセスし、常駐させる。
QueryWorkingSetExでページのWorking Set情報を取得する。Sharedビットを見る。Shared == 0なら、その常駐ページはPrivateです。
VMMapでも、領域のTypeだけでなく、Working SetのPrivate/Shareable内訳を見ます。
6.1. Private Bytesが増えないこともある
FILE_MAP_COPYでは、プロセスが将来ビュー内の全ページへ書く可能性があります。
そのためWindowsは、マップ時点でビュー全体に相当するCommit chargeを取ります。6
したがって、最初の1ページへ書いてもPrivate Bytesがその瞬間に4KiB増えるとは限りません。
CoW観測で優先する指標は次です。
QueryWorkingSetExのSharedビット- VMMapのPrivate WS / Shareable WS
- RAMMapの物理ページ情報
- Private Bytesは補助情報
「書いた瞬間にPrivate Bytesが増えたか」だけを合否判定にすると、正しいCoWを見落とします。
7. Cache Managerとの接点 ── 3つの経路を分ける
「EXE・DLLのロードも、ファイルキャッシュも、共有メモリも、全部セクション」と言うと全体像はつかめますが、実装を1個のオブジェクトへ潰してはいけません。
ファイルストリームには、メモリマネージャーとCache Managerが使うSECTION_OBJECT_POINTERSがあります。
typedef struct _SECTION_OBJECT_POINTERS {
PVOID DataSectionObject;
PVOID SharedCacheMap;
PVOID ImageSectionObject;
} SECTION_OBJECT_POINTERS;
DataSectionObject: データファイルのセクション状態SharedCacheMap: Cache Managerが追うキャッシュビューImageSectionObject: 実行イメージのセクション状態
Microsoftのドキュメントは、この構造がファイルオブジェクトをファイルストリームのセクションと結び、メモリ上の内容とキャッシュ情報を追うと説明しています。5
ここで、I/O連載とメモリ連載が接続します。ただし、3つの経路を一つに潰してはいけません。
- cached
ReadFile/WriteFileは、Cache ManagerのSharedCacheMapとキャッシュビューを使います。 - データファイルのマッピングフォルトは、メモリマネージャーが
DataSectionObject側で処理します。同じファイルストリーム上のcached I/Oとは、内容の整合性を保つよう協調します。 - EXE/DLLのイメージフォルトは、メモリマネージャーが
ImageSectionObjectとページングI/Oで処理します。Cache ManagerのSharedCacheMapを経由する経路ではありません。
共通点は「すべてがCache Managerへ入る」ことではなく、同じファイルストリームがSECTION_OBJECT_POINTERSを介して、キャッシュ、データセクション、イメージセクションという別々の状態を結び付けていることです。5
キャッシュ読み書き、Lazy Writer、Cc/Mmの関係は「Windows I/Oの深層(第4回) ── キャッシュマネージャーとWriteFile」で扱っています。
なお、メモリマップトビューとReadFile/WriteFileを混在させた場合、常に同じ瞬間の内容が見えるとは保証されません。同期、flush、ファイル共有モードを含めた設計が必要です。36
8. オブジェクトとビューの寿命
CreateFileMappingのハンドルを閉じただけでは、既存ビューは消えません。
ビューはセクションへの内部参照を持ち、すべてのビューをUnmapViewOfFileし、すべてのハンドルをCloseHandleして初めて、オブジェクトを破棄できる状態になります。3
UnmapViewOfFile(view);
CloseHandle(mapping);
CloseHandle(file);
この寿命の分離は、「ファイルを閉じたのに使用中」の原因になります。
ファイルハンドルを閉じても、イメージセクションやデータビューがファイルストリームを参照していれば、ファイルの最終的なcloseは後になります。
I/O側のcleanup/closeとの関係は「Windows I/Oの深層(第1回)」、実装上の落とし穴は「共有メモリの落とし穴と実務ベストプラクティス」も参照してください。
9. 自分の目で確かめる
9.1. 同じDLLを2プロセスで見る
- Process Explorerを管理者として起動します。
cmd.exeを2つ起動します。- View > Lower Pane View > DLLsを選びます。
- 両プロセスで同じDLLのパスとマッピングを確認します。
- VMMapで各
cmd.exeを開き、ImagesのWorking Set、Private、Shareableを比較します。
Process Explorerで同じDLLが見えることは、両方が同じイメージをマップしている証拠です。
ただし、それだけで各ページのPFN一致まで証明したことにはなりません。
VMMapのShareable内訳、RAMMap、QueryWorkingSetExを組み合わせて、ページ単位の共有を確認します。Process ExplorerとVMMapはSysinternalsが提供しています。1011
9.2. FILE_MAP_COPYを2プロセスで観測する
次のプログラムは、同じファイルをCoWビューとしてマップし、QueryWorkingSetExのSharedビットを表示します。ファイルマッピングオブジェクトはPAGE_READONLYで作成しますが、この保護はFILE_MAP_COPYのビューと互換で、ビュー側の最初の書き込みがCoWを発生させます。3
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <psapi.h>
#include <cstdio>
#include <cwchar>
#pragma comment(lib, "Psapi.lib")
void PrintPage(const char* stage, void* address)
{
MEMORY_BASIC_INFORMATION mbi{};
if (!VirtualQuery(address, &mbi, sizeof(mbi))) {
std::printf("VirtualQuery failed: %lu\n", GetLastError());
return;
}
for (int attempt = 0; attempt < 3; ++attempt) {
// The page may have been trimmed while the user was waiting.
// Touch it immediately before querying the working-set attributes.
volatile unsigned char resident =
*static_cast<volatile unsigned char*>(address);
(void)resident;
PSAPI_WORKING_SET_EX_INFORMATION ws{};
ws.VirtualAddress = address;
if (!QueryWorkingSetEx(GetCurrentProcess(), &ws, sizeof(ws))) {
std::printf("QueryWorkingSetEx failed: %lu\n", GetLastError());
return;
}
if (!ws.VirtualAttributes.Valid) {
Sleep(0);
continue;
}
std::printf(
"%s: Type=0x%lx Valid=1 Shared=%llu ShareCount=%llu\n",
stage,
static_cast<unsigned long>(mbi.Type),
static_cast<unsigned long long>(ws.VirtualAttributes.Shared),
static_cast<unsigned long long>(ws.VirtualAttributes.ShareCount));
return;
}
std::printf(
"%s: page is not resident; Shared/ShareCount were not interpreted\n",
stage);
}
int wmain(int argc, wchar_t** argv)
{
if (argc != 3) {
std::fwprintf(stderr, L"usage: cow_demo <file> <read|write>\n");
return 2;
}
HANDLE file = CreateFileW(
argv[1], GENERIC_READ, FILE_SHARE_READ,
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (file == INVALID_HANDLE_VALUE) return 3;
HANDLE mapping = CreateFileMappingW(
file, nullptr, PAGE_READONLY, 0, 0, nullptr);
if (!mapping) {
CloseHandle(file);
return 4;
}
auto* view = static_cast<unsigned char*>(
MapViewOfFile(mapping, FILE_MAP_COPY, 0, 0, 0));
if (!view) {
CloseHandle(mapping);
CloseHandle(file);
return 5;
}
volatile unsigned char value = view[0];
(void)value;
std::puts("Start the other process. When both are waiting, press Enter...");
(void)std::getchar();
PrintPage("before", view);
if (std::wcscmp(argv[2], L"write") == 0) {
std::puts("Press Enter to trigger copy-on-write...");
(void)std::getchar();
view[0] ^= 0x5a;
PrintPage("after write", view);
} else {
std::puts("After the writer changes its page, press Enter...");
(void)std::getchar();
PrintPage("reader after peer write", view);
}
std::puts("Press Enter to exit...");
(void)std::getchar();
UnmapViewOfFile(view);
CloseHandle(mapping);
CloseHandle(file);
}
ビルドと準備を行います。
cl /std:c++20 /EHsc /W4 cow_demo.cpp
$path = "$env:TEMP\\cow-demo.bin"
[IO.File]::WriteAllBytes($path, [byte[]]::new(65536))
2つのコンソールで同じファイルを開きます。
.\\cow_demo.exe "$env:TEMP\\cow-demo.bin" read
.\\cow_demo.exe "$env:TEMP\\cow-demo.bin" write
両方を起動したら、まずread側、次にwrite側でEnterを押し、どちらもbeforeでSharedが立っていることを確認します。
続いてwrite側でもう一度Enterを押すと、そのプロセスのページはSharedが0になります。その後read側でEnterを押すと、reader側が元ページを読み続けていることを確認できます。
VirtualQueryのTypeは、書き込み後もMEM_MAPPEDです。
PrintPageは照会直前に対象ページを再タッチし、Valid == 0ならSharedとShareCountを解釈せず最大3回再試行します。それでも常駐しなければ結果を出さず、その旨を表示します。タイミングやメモリ圧力でShareCountは変わり得るため、固定値ではなく、Valid == 1で確認できたSharedビットの変化を見ます。
10. 実務で避けたい5つの誤読
10.1. 「共有メモリは同じ仮想アドレスになる」
共有されるのはセクションと物理ページです。
ビューの仮想アドレスはプロセスごとに違い得るため、生ポインターではなくオフセットを保存します。
10.2. 「同じDLLなら全ページが必ず共有される」
cleanなコードページは共有しやすい一方、再配置、writable section、CoW、計測時点の常駐状態によりPrivateページも生まれます。
10.3. 「CoW後はMEM_PRIVATEになる」
VirtualQueryのTypeはMEM_MAPPEDまたはMEM_IMAGEのままです。
実際の共有状態はQueryWorkingSetExで確認します。7
10.4. 「Private Bytesが増えなければCoWしていない」
FILE_MAP_COPYはビュー全体へ先にCommitを課します。
Private WSとSharedビットを優先します。6
10.5. 「共有ページなら同期は不要」
同じ物理ページが見えることと、複数CPUコアから安全に更新できることは別です。
原子性、メモリ順序、排他、クラッシュ時の中間状態、バージョン互換性を設計します。
Excel COM相互運用でプロセスが残る問題も、参照と寿命を分けて追う必要があります。「Excel COM相互運用でプロセスが残る原因」も参照してください。
11. まとめ
- セクションオブジェクトは共有可能なメモリ範囲を表し、各プロセスはビューとして自分の仮想空間へマップします。1
- 同じセクションの同じオフセットは、異なる仮想アドレスから同じ物理ページへ対応付けられます。2
- ファイルバックセクションは実ファイル、ページファイルバックセクションは名前付き共有メモリなどを支えます。9
- EXE/DLLはイメージセクション、通常ファイルはデータセクションとして扱われ、保護と書き戻し先が異なります。3
- CoWは読み取り中の物理ページを共有し、最初の書き込みでそのページだけコピーしてPTEを差し替えます。4
- CoW後も
VirtualQueryはMEM_MAPPED/MEM_IMAGEを返すため、QueryWorkingSetExのSharedビットで確認します。7 FILE_MAP_COPYではビュー全体のCommitが先に課されるため、Private BytesだけでCoWを判定できません。6- Cache Managerのcached I/O、データマッピング、イメージマッピングは、それぞれ
SharedCacheMap、DataSectionObject、ImageSectionObjectを使う別経路として、同じファイルストリーム上で結び付きます。5 - ビューとハンドルの寿命、同期、ACL、オフセット設計まで含めて初めて安全な共有メモリになります。
これで「Windowsメモリの深層」全3回は完結です。
仮想アドレスをReserve/Commitし、ページフォルトで物理ページを得て、Working Setからページリストへ移し、セクションを通じて共有し、書いたページだけCoWで分岐する──Windowsのメモリ管理は、この一連の流れとしてつながっています。
関連記事
- Windowsメモリの深層(第1回) ── 仮想アドレスが物理RAMに変わる瞬間
- Windowsメモリの深層(第2回) ── 物理ページの一生
- Windows I/Oの深層(第4回) ── キャッシュマネージャーとWriteFile
- 共有メモリの落とし穴と実務ベストプラクティス
- Excel COM相互運用でプロセスが残る原因
- Process Explorer / Handle / VMMap実践
関連する相談領域
合同会社小村ソフトでは、Windowsアプリケーションの共有メモリ、ファイルマッピング、DLLロード、ファイルロック、プロセス間通信、メモリ使用量の不具合調査を扱っています。
参考リンク
-
Microsoft Learn, Section Objects and Views. セクションオブジェクトが共有可能なメモリ範囲を表し、各プロセスがセクションの一部をビューとしてマップすることについて。 ↩ ↩2 ↩3
-
Microsoft Learn, File-Backed and Page-File-Backed Sections. ファイルバック/ページファイルバックセクション、CoW、異なるプロセスの仮想アドレスから同じ物理メモリを共有できることについて。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, CreateFileMappingW function. ファイルマッピングオブジェクト、ページファイルバック、
SEC_IMAGE、ビューとハンドルの寿命、同じファイルを支えるビューの整合性について。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 -
Microsoft Learn, Memory Protection. 複数プロセスが同じDLLの物理ページを共有し、一方の書き込み時に新しい物理ページへコピーしてPTEを更新するCoWについて。 ↩ ↩2 ↩3
-
Microsoft Learn, SECTION_OBJECT_POINTERS structure. DataSectionObject、SharedCacheMap、ImageSectionObjectが、ファイルストリームのマッピングとキャッシュ情報をメモリマネージャー/Cache Managerへ結ぶことについて。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, MapViewOfFileEx function.
FILE_MAP_COPYのCoW、私有ページがページファイルで支えられること、ビュー全体へCommit chargeを課すこと、仮想アドレスではなくオフセットを保存すべきことについて。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 -
Microsoft Learn, VirtualQuery function. CoW後もTypeが
MEM_MAPPED/MEM_IMAGEのままで、QueryWorkingSetExのSharedビットにより私有化を確認できることについて。 ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, Managing Memory Sections. ビューがアクセスされるまで物理メモリは割り当てられず、初回アクセスのページフォルトでファイル内容を読み込むことについて。 ↩
-
Microsoft Learn, Sharing Files and Memory. 同じファイルマッピングオブジェクトを名前・ハンドルで共有すること、
INVALID_HANDLE_VALUEでページファイルバック共有メモリを作ること、同期が別途必要なことについて。 ↩ ↩2 ↩3 -
Microsoft Learn, Process Explorer - Sysinternals. Process Explorerがプロセスのハンドルやロード済みDLL/メモリマップトファイルを表示できることについて。 ↩
-
Microsoft Learn, VMMap - Sysinternals. VMMapがプロセスの仮想メモリをImage、Mapped File、Privateなどに分解し、Working SetのPrivate/Shareable内訳を表示することについて。 ↩
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
Windowsメモリの深層(第2回) ── 物理ページの一生:5つのリストとページファイルの真実
PFNデータベース、Standby、Modified、メモリ圧縮、ページファイルをつなぎ、Working Setから外れた物理ページの行き先を解説します。
Windowsメモリの深層(第1回) ── 仮想アドレスが物理RAMに変わる瞬間:ページフォルトの一部始終
VirtualAlloc、VAD、ページテーブル、TLB、デマンドゼロ、ハードフォルトをつなぎ、仮想アドレスが物理RAMへ割り当てられる瞬間を解説します。
Windowsの「メモリ使用量」は何を表しているのか ── Working Set・Private Bytes・Commit・ページファイルを正しく読む
タスクマネージャーのメモリ、Working Set、Private Bytes、Commitは同じ値ではありません。Windowsの仮想メモリと物理メモリの関係、ページファイルの役割、メモリ不足やリーク調査で見るべき指標を解説します。
Windowsのプロセス間通信をどう選ぶか ── 名前付きパイプ / TCP / gRPC / 共有メモリ / COM 判断表
Windowsアプリ同士の連携手段をどう選ぶか。名前付きパイプ、ローカルTCP、gRPC、共有メモリ、ファイル連携、COMの得意分野と落とし穴を判断表で整理し、定番構成と名前付きパイプの実装例まで解説します。
Windows DLL名前解決の仕組み - 検索順序とSxS
Windows の DLL 名前解決を、DLL search order、Known DLLs、loaded-module list、API set、SxS manifest、LoadLibrary 系 API の影響まで実務向けに整理します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
業務アプリ、装置連携、通信ツールなどの Windows ソフト開発を支援します。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- 同じDLLを使うプロセスごとに、DLL全体がRAMへ複製されますか?
- 通常は複製されません。同じイメージの変更されていないページは、各プロセスの異なる仮想アドレスから同じ物理ページへ対応付けられます。書き込みが必要なページだけ、コピーオンライトなどによりプロセス固有の物理ページになります。
- CreateFileMappingは、その時点でプロセスへメモリを割り当てますか?
- CreateFileMappingはファイルマッピングオブジェクトを作りますが、プロセスの仮想空間へ見えるようにするのはMapViewOfFileです。さらに、ビューの物理ページは通常、初めてアクセスしたページからページフォルトで実体化されます。
- FILE_MAP_WRITEとFILE_MAP_COPYは何が違いますか?
- FILE_MAP_WRITEの変更は共有されたファイルデータ側へ反映される書き込みです。FILE_MAP_COPYは初期ページを共有しますが、書いたページだけプロセス専用コピーとなり、変更は元ファイルへ書き戻されず、ビューを外すと失われます。
- コピーオンライト後はVirtualQueryがMEM_PRIVATEを返しますか?
- 返しません。データビューならMEM_MAPPED、イメージビューならMEM_IMAGEのままです。ページが実際に私有化されたかは、ページを常駐させたうえでQueryWorkingSetExのSharedビットを見るのが確実です。
- 共有メモリへ生のポインターを保存してよいですか?
- 通常は避けます。同じセクションでも各プロセスのビューが同じ仮想アドレスへ配置される保証はありません。共有構造ではベースからのオフセット、固定幅整数、明示したレイアウトと同期方式を使います。