對 VirtualAlloc 傳入 MEM_COMMIT 時,Commit 會立刻增加。不過 Working Set 不一定會同步增加同樣的量。那麼,你以為已經配置的記憶體在哪裡?
答案是 大多數頁還沒有對應的實體 RAM。Windows 會把實體頁的指派延遲到應用程式真正碰到該頁。第一次存取讓 CPU 發出頁面錯誤時,記憶體管理員會檢查 VAD、PTE、保護屬性與後備存放區,必要時一頁一頁把 RAM 綁上去。1
本文跟著「你碰到的第一個位元組」走到實體 RAM。若想先整理 Working Set 與 Commit 等數字的意義,請看入門篇「Windows 的「記憶體使用量」代表什麼 ── 正確解讀 Working Set、Private Bytes、Commit 與分頁檔」。本系列不重定義那裡用過的術語,而是從機制面追「為什麼會出現那個數字」。
「Windows 記憶體的深層」全 3 回
- 第 1 回(本文):虛擬位址與頁面錯誤
跟著以VirtualAlloc配置的區域何時取得實體 RAM。 - 第 2 回:實體頁的一生
跟著離開 Working Set 的頁如何在 Modified、Standby、Free、Zeroed 之間移動。 - 第 3 回:區段物件與寫入時複製
跟著 DLL、檔案對應與共用記憶體為何能共用實體頁。
第 1 回要回答的問題只有一個。
已認可的虛擬位址,在哪一瞬間變成實體 RAM?
預定讀者是想從機制理解 Windows 應用記憶體用量、啟動直後的頁面錯誤、0xC0000005,以及 VMMap 與 PerfMon 數字的開發者與營運人員。前提環境是 Windows 10/11 或現行 Windows Server,必要背景是指標與 VirtualAlloc 基礎;不需要頁面表位元配置或核心偵錯工具經驗。難度為中級。會用到內部結構名稱,但不假設依賴特定 Windows 組建的未公開配置。
1. 先講結論
一般私有記憶體的流程,可以用一句話說完。
Reserve 預留虛擬位址範圍,Commit 對系統認可上限計入 commit charge 以保證將來有地方保存內容,第一次存取的頁面錯誤才指派實體頁。
也就是說,MEM_COMMIT 不是「現在立刻配置 RAM」的命令。Microsoft 的 VirtualAlloc 文件也保證已認可頁的初始內容為零,同時說明實際實體頁要到存取該虛擬位址才會被指派。1
不過,說「Reserve/Commit 只寫進 VAD」也不準確。實務上,Reserve 主要建立代表虛擬位址範圍與屬性的 VAD,Commit 則增加系統的 Commit Total 並記錄該範圍的認可狀態。中間頁面表層級與個別 PTE 會在需要時延遲建立,與實體 RAM 的最終綁定通常發生在第一次存取。
Commit 不是空頭支票,而是 全系統保證將來能把內容保存在 RAM 或適當後備存放區的承諾。把這項承諾一頁一頁兌現的入口,就是頁面錯誤。
flowchart TB
accTitle: Reserve、Commit 與第一次存取時發生的事
accDescr: MEM_RESERVE 把範圍與屬性寫進 VAD,MEM_COMMIT 消耗 Commit Total 以承諾存放,第一次存取的頁面錯誤指派實體頁並加入 Working Set
reserve["1. MEM_RESERVE"] --> commit["2. MEM_COMMIT"]
commit --> touch["3. 第一次存取(Touch)"]
reserve -.-> vad["把範圍與屬性記進 VAD"]
commit -.-> charge["消耗 Commit Total(尚無實體頁)"]
touch --> fault["頁面錯誤"]
fault --> zero["把已歸零的實體頁綁到 PTE"]
zero --> ws["加入 Working Set 並重執行指令"]
圖 1: Reserve、Commit 與 Touch 是分開的事件。實體 RAM 只在最後一步、也就是第一次存取時被綁上。
2. 追蹤虛擬頁的三本帳簿
要理解從虛擬位址到實體 RAM 的路徑,必須分清 Windows 保存的三種帳簿。
| 帳簿 | 單位 | 角色 |
|---|---|---|
| VAD | 虛擬位址範圍 | 管理區域是什麼、Reserve/Commit、保護、與區段的對應 |
| 頁面表 / PTE | 虛擬頁 | 表示目前對實體頁的轉譯,或尚未實現的狀態 |
| PFN 資料庫 | 實體頁 | 追蹤每頁 RAM 的擁有者、參照與狀態 |
VAD 握的是 範圍,PTE 握的是 虛擬頁,PFN 資料庫握的是 實體頁。頁面錯誤處理常式交叉核對這些帳簿,決定存取能否繼續。
flowchart TB
accTitle: 從虛擬位址到實體 RAM 的三本帳簿
accDescr: 虛擬位址由 VAD 以範圍粒度管理、由 PTE 以虛擬頁粒度管理,PTE 的轉譯目標實體頁則由 PFN 資料庫以實體頁粒度追蹤
va["虛擬位址"] --> vad["VAD(範圍帳簿)"]
va --> pte["PTE(虛擬頁帳簿)"]
vad -.->|判斷 Reserve/Commit 與保護| pte
pte -->|有效轉譯| pfn["PFN 資料庫(實體頁帳簿)"]
pfn --> ram["實體 RAM 頁"]
圖 2: 粒度不同的三本帳簿。錯誤處理交叉核對 VAD 與 PTE,再把結果反映到 PFN 側。
本文的主角是 VAD 與 PTE。PFN 資料庫會在第 2 回從實體頁那一側來看。
3. Reserve、Commit 與 Touch 是分開的事件
3.1. Reserve ── 先佔住位址
先預留一段連續 256MiB 的虛擬位址範圍。
void* base = VirtualAlloc(
nullptr,
256ull * 1024 * 1024,
MEM_RESERVE,
PAGE_NOACCESS);
此刻發生的事,只是在處理序的虛擬空間裡把位址佔住,讓其他配置不能用這段範圍。MEM_RESERVE 不會在 RAM 或分頁檔指派實體存放區。1
64 位元處理序的虛擬空間很大,先 Reserve 大範圍、之後只 Commit 需要的部分,實務上很常見。
3.2. Commit ── 承諾將來保得住
接著把已預留的範圍 Commit。
void* committed = VirtualAlloc(
base,
256ull * 1024 * 1024,
MEM_COMMIT,
PAGE_READWRITE);
成功時,反映在系統 Commit Total——通常也反映在處理序 Private Bytes——的承諾量會增加。即便如此,也不會一次排出 256MiB 的實體頁。一般頁在第一次存取前仍保持實體未指派。12
那麼 Commit 的意義是什麼?在於 系統無法承擔這項承諾時,可以在 Commit 當下回傳失敗,而不是用到一半才失敗。
3.3. Touch ── 實體頁變得必要的時候
最後,下面這次指派第一次寫入第一頁。
static_cast<unsigned char*>(base)[0] = 1;
CPU 試著把虛擬位址轉成實體位址,但 PTE 還沒有對實體頁的有效轉譯。頁面錯誤就發生在這裡。
接到控制權的記憶體管理員判斷這是「對已認可、可寫私有頁的第一次存取」,取得已歸零的實體頁、綁到 PTE,並加入 Working Set。然後重執行失敗的寫入指令。
對應用來說只是一次指派,內部卻是 在指派途中進入核心、指派實體頁,再回到同一條指令。
4. VAD ── 虛擬空間的範圍帳簿
VAD 是 Virtual Address Descriptor 的縮寫,Windows 用 VAD 樹管理處理序正在使用的位址範圍。用 WinDbg 的 !vad 可以查看起迄 VPN、Commit、保護屬性、Private/Mapped、Control Area 等。3
VAD 記錄的代表性資訊包括下列項目。
- 位址範圍的起點與終點
- Private、Mapped、Image 等種類
- Reserve/Commit 狀態
- 讀、寫、執行、寫入時複製等保護
- 與檔案或區段的對應
- 防護頁等特殊屬性
用範圍管理是為了效率。256MiB 以 4KiB 計是 65,536 頁。與其一開始就為每一頁建完整管理結構,不如在 VAD 裡記下「這段連續範圍是一次預留」,需要時再實現各頁,比較不浪費。
4.1. 找到 VAD 不保證能恢復
「在 VAD 裡就能解決錯誤,不在就存取違規」當作入門說明很方便,但過度簡化。即使找到 VAD,下列情況仍無法繼續一般存取。
- 只有 Reserve,目標頁尚未認可
PAGE_NOACCESS- 對唯讀頁寫入
- 在不可執行頁上執行指令
- 第一次碰到防護頁
- 碰到區段有效範圍之外
反過來說,即使 PTE 無效,只要 VAD 與 PTE 的軟體狀態顯示這是正當存取,就能以 demand-zero、Transition 還原、換入或 CoW 解決。更精確的答案是 把 VAD、PTE、保護屬性與存取類型一起判斷。
5. 頁面表與 TLB
應用握著的指標是虛擬位址。CPU 要存取 RAM,必須把虛擬頁號轉成實體頁號。那份階層轉譯表就是頁面表,葉項目是 PTE(Page Table Entry)。
有效 PTE 在概念上握有 PFN、讀寫執行保護、使用者模式權限、Accessed/Dirty 等資訊。實際位元配置依 CPU 與 Windows 版本而異。
每次都走頁面表會太慢,所以 CPU 把最近的轉譯快取在 TLB(Translation Lookaside Buffer)。位址轉譯依這個順序進行。
- 若 TLB 有轉譯且存取符合該保護,就用那個結果。
- 若 TLB 沒有轉譯,CPU 走頁面表。
- 若有有效 PTE 且保護也符合,就登錄到 TLB 並繼續執行。
- 若沒有有效轉譯,或發生保護違規,就走進頁面錯誤入口。保護檢查在轉譯來自 TLB 時也會做。
這個流程顯示 TLB 未命中與頁面錯誤是兩件事。若只是 TLB 沒有轉譯而 PTE 有效,發生的只是頁面表走訪。反過來說,即使 TLB 已有轉譯,對唯讀頁寫入或在不可執行頁上執行指令這類保護違規,仍會走進頁面錯誤入口。因此對 CoW 頁寫入,即使轉譯已快取,仍可能錯誤。
flowchart TB
accTitle: 位址轉譯流程與頁面錯誤入口
accDescr: 即使 TLB 有轉譯,保護不符仍走進頁面錯誤入口。TLB 沒有轉譯就走頁面表;有效 PTE 且保護也符合則登錄 TLB 並繼續,無效轉譯或保護違規則走進頁面錯誤入口
access["記憶體存取"] --> tlb{"TLB 有轉譯嗎?"}
tlb -->|有| perm{"存取符合保護嗎?"}
perm -->|符合| go["用該轉譯繼續"]
perm -->|保護違規| entry["走進頁面錯誤入口"]
tlb -->|沒有| walk["走頁面表"]
walk --> valid{"有效 PTE 且保護也符合?"}
valid -->|是| register["登錄 TLB 並繼續(無錯誤)"]
valid -->|無效或保護違規| entry
圖 3: TLB 未命中可以用走頁面表解決。轉譯無效或有保護違規時才走進頁面錯誤;保護違規在 TLB 命中時也會發生。
5.1. 無效 PTE 不是空白
即使是無效 PTE 也不是空的。Windows 從無效 PTE 的軟體狀態區分下列情況。
- 從未實現的 demand-zero 頁
- 仍留在 RAM 的 Transition 頁
- 參照 Prototype PTE 的共用頁
- 保存在分頁檔的私有頁
- 保護違規或無效區域
CPU 的工作只是判斷「這不是普通的有效轉譯」並交給核心;之後的意義由記憶體管理員補上。
6. 頁面錯誤從頭到尾
讓我們用六個階段跟著對已認可私有頁的第一次寫入。
- CPU 嘗試寫入。
它檢查 TLB 與頁面表,但目標 PTE 沒有有效 PFN。 - CPU 發出頁面錯誤。
把發生錯誤的虛擬位址、讀寫執行類型、使用者/核心,以及問題是缺少轉譯還是保護違規,交給核心。 - 記憶體管理員檢查 VAD 與 PTE。
判斷頁是否已認可、保護是否符合,以及屬於 demand-zero、Transition、共用、換入、CoW 或例外的哪一種。 - 若是 demand-zero,取得已歸零的實體頁。
新交出的頁必須是零,以免洩漏另一個處理序的資料。 - 更新 PTE 與 PFN 管理資訊。
在 PTE 設定 PFN 與保護,把實體頁設為 Active,並加入處理序的 Working Set。 - 重執行失敗的指令。
因為錯誤正常解決,不會把使用者模式例外交出去,應用像平常一樣繼續指派。
ETW 頁面錯誤事件也把 Transition、Demand Zero、Copy-on-Write、Guard Page、Hard Page Fault、Access Violation 記成不同種類。4
所以頁面錯誤一開始就不是「異常」這個詞。它是 CPU 無法走普通路徑轉譯時,請作業系統判斷的共用入口。
flowchart LR
accTitle: 頁面錯誤解決的分支
accDescr: 記憶體管理員判斷 VAD、PTE、保護屬性與存取類型,分派到 demand-zero、重新接上仍在 RAM 的頁、從後備存放區來的硬錯誤、寫入時複製、防護頁通知或例外
faultIn["發生頁面錯誤"] --> judge["判斷 VAD、PTE、保護、類型"]
judge -->|第一次存取| dz["Demand-zero(軟)"]
judge -->|仍在 RAM| soft["從 Standby 重接(軟)"]
judge -->|需要讀磁碟| hard["硬錯誤(磁碟 I/O)"]
judge -->|CoW 寫入| cow["複製並更換 PTE"]
judge -->|防護頁| guard["清掉防護並通知"]
judge -->|無法解決| av["例外(0xC0000005 等)"]
圖 4: 從同一入口進來的錯誤,依判斷分成六種結果。防護頁細節見第 9 節。
7. Demand-zero ── 不讀磁碟的軟錯誤
Demand-zero 是第一次碰到已認可私有頁時發生的代表性軟錯誤。Microsoft 的 Working Set 文件也把「處理序第一次參照已配置的虛擬頁」列為軟錯誤的例子。5
Demand-zero 有下列特徵。
- 不必從磁碟讀原始資料
- 初始內容為零
- 綁上可用的實體頁
- Working Set 與累計 Page Fault Count 增加
- 單靠這次處理不會增加
Memory\\Pages Input/sec
因此啟動直後 Page Faults/sec 飆高,本身並不代表儲存體是瓶頸。
延遲配置的取捨也值得整理。若 Commit 了 256MiB 卻實際只用 8MiB,把剩下 248MiB 留在 RAM 外是合理的。代價是第一次存取要付錯誤處理成本。對延遲敏感的工作,有一種設計是開始前先碰每一頁做預先錯誤(prefault),但那是先增加 RAM 常駐的取捨。
8. 軟錯誤與硬錯誤
8.1. 軟錯誤
軟錯誤是不必對後備存放區做讀取 I/O 就能解決的錯誤。代表性例子包括下列項目。
- Demand-zero
- 把仍在 Standby/Transition 的頁重新接上
- 接上已在另一處理序 Working Set 的共用頁
- 接上已預先擷取的頁
- 原始頁仍常駐的寫入時複製
核心轉換、鎖、PTE/PFN 更新、TLB 一致性等仍有 CPU 成本,但沒有儲存體等待。5
8.2. 硬錯誤
另一方面,需要的頁不在 RAM 任何地方、必須從後備存放區讀取時,就是硬錯誤。讀取來源不只是分頁檔。
- 被寫出到分頁檔的私有頁
- 記憶體對應檔
- EXE 或 DLL 映像
- 檔案快取參照的資料檔
ETW HardFault 事件包含 FileObject、ReadOffset、ByteCount,因此可以追到實際讀取來源。6
所以 Hard Fault = 讀 pagefile.sys 並不成立。
需要讀後備存放區時,要求會進入 Windows I/O 堆疊。IRP 的發出與完成流程見「Windows I/O 的深層(第 1 回)」,與檔案快取的接點見「Windows I/O 的深層(第 4 回)」。頁在 RAM 裡時,記憶體管理員可以自己回來;不在時就發出 I/O,並讓發生錯誤的執行緒等到完成。
9. 無法解決的錯誤會變成例外
檢查 VAD 與 PTE 後,無法當成正當配置、換入或 CoW 解決的錯誤,會以例外交給使用者模式。
代表性情況是 STATUS_ACCESS_VIOLATION,例外碼 0xC0000005。它發生在對無效位址讀、寫或執行;第一個例外參數指出存取類型,第二個指出違規位址。7
典型模式包括下列項目。
- 讀取 NULL、已釋放位址或陣列外的位址
- 對唯讀頁寫入
- 從 DEP/NX 設成不可執行的頁執行指令
- 碰到尚未認可的預留範圍
PAGE_GUARD 的意義稍有不同。它是對存取的一次性通知:引發 STATUS_GUARD_PAGE_VIOLATION,用於堆疊成長等情況。8
正常的延遲配置、換入、CoW、防護通知,以及最後的存取違規,從 CPU 看來都聚在同一個頁面錯誤入口。決定結果的是 VAD、PTE、保護屬性與存取類型的組合。
10. 自己動手看
到目前為止的流程,可以在自己的機器上觀察。下面的 C++ 程式 Reserve 256MiB、Commit,對每頁寫一個位元組,最後 Release。每個階段都會等 Enter,方便用 VMMap 與 PerfMon 觀察變化。
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <psapi.h>
#include <cstdio>
#include <cstdlib>
#pragma comment(lib, "Psapi.lib")
constexpr SIZE_T kSize = 256ull * 1024 * 1024;
void PrintMemory(const char* stage)
{
PROCESS_MEMORY_COUNTERS_EX c{};
c.cb = sizeof(c);
if (!GetProcessMemoryInfo(
GetCurrentProcess(),
reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&c),
sizeof(c))) {
std::printf("GetProcessMemoryInfo failed: %lu\n", GetLastError());
return;
}
std::printf(
"%-10s WS=%zu MiB Private=%zu MiB Faults=%lu\n",
stage,
c.WorkingSetSize / 1024 / 1024,
c.PrivateUsage / 1024 / 1024,
c.PageFaultCount);
}
void Pause(const char* message)
{
PrintMemory(message);
std::puts("Press Enter...");
(void)std::getchar();
}
int main()
{
SYSTEM_INFO si{};
GetSystemInfo(&si);
std::printf("PID=%lu, page=%lu bytes\n",
GetCurrentProcessId(), si.dwPageSize);
void* base = VirtualAlloc(nullptr, kSize, MEM_RESERVE, PAGE_NOACCESS);
if (!base) {
std::fprintf(stderr, "Reserve failed: %lu\n", GetLastError());
return EXIT_FAILURE;
}
Pause("reserved");
if (!VirtualAlloc(base, kSize, MEM_COMMIT, PAGE_READWRITE)) {
std::fprintf(stderr, "Commit failed: %lu\n", GetLastError());
VirtualFree(base, 0, MEM_RELEASE);
return EXIT_FAILURE;
}
Pause("committed");
auto* bytes = static_cast<volatile unsigned char*>(base);
for (SIZE_T offset = 0; offset < kSize; offset += si.dwPageSize) {
bytes[offset] = 1;
}
Pause("touched");
if (!VirtualFree(base, 0, MEM_RELEASE)) {
std::fprintf(stderr, "Release failed: %lu\n", GetLastError());
return EXIT_FAILURE;
}
Pause("released");
}
在 Visual Studio 的 x64 Native Tools Command Prompt 可用下列命令建置。
cl /std:c++20 /EHsc /W4 memory_fault_demo.cpp
10.1. 在 VMMap 看什麼
VMMap 會依類型顯示已預留的虛擬記憶體、Commit、Working Set、Private 與 Shareable。9 各階段預期的變化如下。
| 階段 | 預期變化 |
|---|---|
| Reserve | Address Space Size 增加,但 Commit/WS 不會同步增加同樣的量 |
| Commit | Private Commit 大約增加 256MiB |
| Touch | Working Set 與 Private WS 明顯增加,Fault Count 也增加 |
| Release | 目標範圍消失,Commit 與 WS 下降 |
實際數字會隨執行階段、資安產品、記憶體壓力與觀察時機而變。看的是 各階段之間數字往哪邊移動,不是是否剛好等於 256MiB。
10.2. 在 PerfMon 分開軟與硬
在 PerfMon 把下列計數器放在同一時間軸。
Process(<target>)\\Page Faults/secMemory\\Pages Input/secMemory\\Page Reads/secMemory\\Available MBytesProcess(<target>)\\Working Set - PrivateProcess(<target>)\\Private Bytes
Process\\Page Faults/sec 同時包含軟錯誤與硬錯誤。另一方面,Memory\\Pages Input/sec 是為了解決硬錯誤而從磁碟讀入的頁數。10
在這個程式的 Touch 階段,Page Faults/sec 應該跳升,而 Pages Input/sec 不該升太多。新認可的頁由 demand-zero 實現,不必從磁碟讀原始資料。
多個處理序共用同一名稱時,PerfMon 的 process#1 這類編號可能在重啟後改變。請用顯示 PID 的計數器交叉核對,或用 Process V2 或 ETW/WPA 依 PID 識別。
11. 實務上要避開的三種誤讀
11.1. 「Commit 上去了,所以是 RAM 洩漏」
Commit 是承諾保存的內容量;還沒碰到的頁可能不常駐在 RAM。要判斷洩漏,請看 Private Bytes 的時間序列、配置的拆解,以及處理結束後數字是否回到基準線。
11.2. 「Page Faults/sec 很高,所以磁碟很慢」
軟錯誤沒有磁碟 I/O。把 Page Faults/sec、Pages Input/sec 與儲存體等待分開看,必要時用 ETW HardFault 事件追到來源檔與堆疊。
11.3. 「清空 Working Set 就能修好洩漏」
把頁從 Working Set 拿掉並不釋放 Commit 或擁有權。該頁會走到 Standby 或 Modified,稍後再錯誤回來。修好洩漏需要配置者執行 VirtualFree、堆積釋放、物件解構等。
被拿掉的實體頁去了哪裡,第 2 回會跟著走。
12. 摘要
MEM_RESERVE預留虛擬位址範圍,但不在 RAM 或分頁檔指派實體區域。1MEM_COMMIT消耗 Commit 並保證將來能保存內容,但一般實體頁要到第一次存取才指派。12- VAD 是範圍帳簿,PTE 是虛擬頁帳簿,PFN 資料庫是實體頁帳簿。
- TLB 未命中不是頁面錯誤。PTE 有效時,只走頁面表就能解決。
- Demand-zero、Transition 還原、接上共用頁,是不必磁碟 I/O 就能解決的軟錯誤。5
- 若必須從分頁檔、DLL、EXE 或對應檔讀取,就是硬錯誤。6
- 檢查 VAD、PTE 與保護屬性後仍無法解決,會得到
0xC0000005這類例外。7 - 判斷效能時不要只看
Page Faults/sec;把Pages Input/sec、Available、Working Set、Private Bytes 與儲存體等待放在同一時間軸。
續篇是第 2 回,「實體頁的一生:五份清單與分頁檔的真相」。
Commit 承諾變成實體頁之後,我們從 PFN 資料庫與頁清單跟著該頁離開 Working Set 後去了哪裡。
相關文章
- Windows 的「記憶體使用量」代表什麼 ── 正確解讀 Working Set、Private Bytes、Commit 與分頁檔
- Windows I/O 的深層(第 1 回) ── 所有讀寫都會變成 IRP:I/O 系統全貌
- Windows I/O 的深層(第 4 回) ── 快取管理員:你的 WriteFile 究竟何時送達磁碟
- 用 WinDbg + SOS 解讀當機傾印檔 ── 收集之後的實務分析入門
- Windows 應用的 crash dump 收集入門 - 先搞清楚 WER / ProcDump / WinDbg 怎麼分工
相關諮詢領域
小村軟體有限公司承接 Windows 應用程式記憶體用量、存取違規、啟動延遲、分頁與原生程式碼缺陷的調查。
參考連結
-
Microsoft Learn, VirtualAlloc function. 關於
MEM_RESERVE預留虛擬位址範圍而不指派實體存放區;MEM_COMMIT對系統整體記憶體與分頁檔計入 commit charge;已認可頁的初始內容為零;以及實際實體頁要到被存取才指派。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 -
Microsoft Learn, PERFORMANCE_INFORMATION structure. 關於
CommitTotal是目前系統 Commit 頁數,以及CommitLimit是不擴充分頁檔也能認可的上限。 ↩ ↩2 -
Microsoft Learn, !vad (WinDbg). 關於
!vad顯示 VAD 樹,並可查看起迄 VPN、Commit、Mapped/Private、保護屬性、Control Area 等。 ↩ -
Microsoft Learn, PageFault_TypeGroup1 class. 關於 ETW 區分並記錄 Transition Fault、Demand Zero Fault、Copy-on-Write、Guard Page Fault、Hard Page Fault 與 Access Violation。 ↩
-
Microsoft Learn, Working Set. 關於軟錯誤可不存取後備存放區就能解決,並因另一處理序的 Working Set、Transition、第一次參照的 demand-zero 等而發生。 ↩ ↩2 ↩3
-
Microsoft Learn, PageFault_HardFault class. 關於 HardFault 事件包含 FileObject、ReadOffset、ByteCount、VirtualAddress 與執行緒 ID,因此可追蹤讀取來源。 ↩ ↩2
-
Microsoft Learn, Access Violation C0000005. 關於
0xC0000005發生在對無效記憶體位址讀、寫或執行,以及例外參數指出存取類型與違規位址。 ↩ ↩2 -
Microsoft Learn, Creating Guard Pages. 關於
PAGE_GUARD提供頁面存取的一次性通知,並引發STATUS_GUARD_PAGE_VIOLATION。 ↩ -
Microsoft Learn, VMMap - Sysinternals. 關於 VMMap 依類型拆解已認可虛擬記憶體,並顯示各類型的 Working Set 與詳細位址圖。 ↩
-
Microsoft Learn, Performance Analysis of Logs (PAL) Tool. 關於
Memory\\Pages Input/sec是為了解決硬頁面錯誤而從磁碟讀入的頁數。 ↩
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
Windows 記憶體的深層(第2回) ── 實體頁面的一生:五份清單與分頁檔的真相
把 PFN 資料庫、Standby、Modified、記憶體壓縮與分頁檔串起來,說明實體頁面離開 Working Set 之後會去哪裡。
Windows 記憶體的深層(第3回)── 區段物件與寫時複製:DLL 與檔案對應的真面目
把區段物件、映像與資料對應、共享快取、寫時複製串起來,說明 DLL 與共享記憶體如何共用實體頁面。
Windows 的「記憶體使用量」代表什麼 ── 正確解讀 Working Set、Private Bytes、Commit 與分頁檔
工作管理員的記憶體、Working Set、Private Bytes、Commit 並非同一個數值。本文解說 Windows 虛擬記憶體與實體記憶體的關係、分頁檔的角色,以及在記憶體不足或洩漏調查時應該檢視的指標。
Windows 虛擬化的深層(第 3 回)── 數秒就能啟動的虛擬機器:為什麼 WSL2、Windows Sandbox 與容器這麼輕
為什麼 WSL2 與 Windows Sandbox 能在數秒內啟動、感覺這麼輕?本文從動態基礎映像、direct map、動態記憶體配置到 Hyper-V 隔離容器,解說這些機制。
Windows 虛擬化的深層(第 2 回)── 連核心都看不到的記憶體:VBS、HVCI 與 Credential Guard 如何運作
在相容硬體上全新安裝時,VBS 預設啟用,並用 Hypervisor 與 SLAT 做出比核心更強的隔離。本文解說 VTL、Secure Kernel、HVCI 與 Credential Guard 的結構。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
常見問題
整理諮詢這個主題時常見的問題。
- 對 VirtualAlloc 傳入 MEM_COMMIT 的當下就會配置 RAM 嗎?
- 一般的私有記憶體裡,Commit 會消耗系統的認可餘裕,但對應的實體頁要到第一次存取才會被指派。以寫入首次碰到的頁,是在 demand-zero 錯誤處理中取得實體頁。
- 頁面錯誤代表異常或有效能問題嗎?
- 不是。像 demand-zero 或從 Standby 回來這種沒有磁碟 I/O 的軟錯誤是正常運作。判斷效能時,除了 Page Faults/sec,也要看 Pages Input/sec、儲存體等待與 Available MBytes。
- TLB 未命中和頁面錯誤是同一件事嗎?
- 不是。即使 TLB 沒有轉譯,只要頁面表的 PTE 有效,CPU 只是走表並重新登錄轉譯。PTE 無效或發生保護違規時,才會走進頁面錯誤入口。
- 位址範圍在 VAD 裡,就不會發生存取違規嗎?
- 不一定。除了 VAD 是否存在,記憶體管理員還會評估 Reserve 與 Commit、讀寫執行保護、防護頁、PTE 狀態等。錯誤無法解決時,會得到 0xC0000005 這類例外。
- Page Faults/sec 很高就代表系統 RAM 不夠嗎?
- 單憑這個看不出來。Page Faults/sec 也包含大量軟錯誤。必須和 Memory\Pages Input/sec、Memory\Page Reads/sec、Available MBytes、磁碟等待時間放在同一時間軸上對照。