Page Fault ב-Windows (חלק 1): מתי כתובת וירטואלית מקבלת RAM פיזי

· עודכן בתאריך: · · Windows, ניהול זיכרון, VirtualAlloc, Page Fault, VAD, ניטור ביצועים

היסטוריית עדכונים (גרסה ראשונה, פורסמה בתאריך 20 Aug 2026)
פרסום ראשון
לצטט את המאמר הזה(DOI: 10.5281/zenodo.22176076)

מאמר זה מאוחסן בארכיון Zenodo. להלן גם ה-DOI שתמיד מפנה לגרסה האחרונה וגם ה-DOI המקובע לגרסה שאתם קוראים.

Go Komura (2026). Page Fault ב-Windows (חלק 1): מתי כתובת וירטואלית מקבלת RAM פיזי. KomuraSoft LLC. https://doi.org/10.5281/zenodo.22176076 https://comcomponent.com/he/blog/windows-memory-internals-page-fault/

DOI (הגרסה האחרונה)
10.5281/zenodo.22176076
DOI (הגרסה הזו)
10.5281/zenodo.22176077

כשמעבירים MEM_COMMIT ל-VirtualAlloc, Commit עולה באותו רגע. Working Set לא בהכרח גדל באותו סכום. אז איפה הזיכרון שחשבתם שהקציתם?

התשובה: לרוב הדפים עדיין אין RAM פיזי מתאים. Windows דוחה הקצאת דף פיזי עד שהאפליקציה באמת נוגעת בדף. הגישה הראשונה גורמת למעבד להרים page fault; אז memory manager בודק VAD, PTE, protection ו-backing store, וקושר RAM דף-דף אם צריך.1

המאמר עוקב אחרי הדרך שעובר «הבית הראשון שנגעתם בו» עד שהוא מגיע ל-RAM. אם תרצו קודם את משמעות המספרים עצמם — Working Set, Commit — ראו את מאמר הפתיחה «מה באמת אומר “שימוש בזיכרון” של Windows — Working Set, Private Bytes, Commit ו-pagefile». הסדרה הזו לא מגדירה מחדש את המונחים משם; היא חופרת מהמנגנון ב«למה המספר יוצא ככה».

«מעמקי הזיכרון של Windows» — שלושת החלקים

  1. חלק 1 (המאמר הזה): כתובות וירטואליות ו-page fault
    מתי אזור שהוקצה ב-VirtualAlloc מקבל RAM פיזי.
  2. חלק 2: מחזור החיים של דף פיזי
    לאן הולך דף שיוצא מ-Working Set: Modified, Standby, Free, Zeroed.
  3. חלק 3: section object ו-copy-on-write
    למה DLL, file mapping ו-shared memory יכולים לחלוק דפים פיזיים.

השאלה שחלק 1 עונה עליה היא אחת.

באיזה רגע כתובת וירטואלית שכבר ב-Commit הופכת ל-RAM פיזי?

קהל היעד הוא מפתחים ומפעילים שרוצים להבין מהמנגנון את שימוש הזיכרון של אפליקציות Windows, page fault מיד אחרי ההפעלה, 0xC0000005, ואת המספרים ב-VMMap וב-PerfMon. הסביבה היא Windows 10/11 או Windows Server עדכני. הרקע הנדרש הוא pointers ויסודות VirtualAlloc; אין צורך בפריסת סיביות של page table או ב-kernel debugger. רמת הקושי בינונית. אנחנו משתמשים בשמות מבנים פנימיים, אבל לא מניחים layout לא מתועד שתלוי ב-build מסוים של Windows.

1. קודם המסקנה

זרימת הזיכרון הפרטי הרגילה, בשורה אחת:

Reserve תופס טווח כתובות וירטואליות, Commit גובה commit charge מול Commit Limit כדי להבטיח מקום עתידי לתוכן, וה-page fault בגישה הראשונה מקצה את הדף הפיזי.

כלומר MEM_COMMIT אינו פקודה «הקצה RAM עכשיו». גם תיעוד Microsoft של VirtualAlloc מבטיח שתוכן ההתחלה של דף ב-Commit הוא אפס, ומסביר שהדף הפיזי בפועל לא מוקצה עד שניגשים לכתובת הווירטואלית.1

גם לא מדויק לטעון ש-«Reserve/Commit כותבים רק ב-VAD». בפועל Reserve יוצר בעיקר VAD שמייצג את טווח הכתובות והמאפיינים, ו-Commit מגדיל את Commit Total של המערכת ורושם את מצב ה-commit של הטווח. רמות ביניים של page table ו-PTE בודדות נבנות lazy כשצריך, והקישור הסופי ל-RAM קורה בדרך כלל בגישה הראשונה.

Commit אינו הבטחה ריקה; זו הבטחה ברמת המערכת שאפשר יהיה לשמור את התוכן בעתיד ב-RAM או ב-backing store מתאים. נקודת הכניסה שמממשת את ההבטחה הזו דף-דף היא ה-page fault.

מה קורה ב-Reserve, ב-Commit ובגישה הראשונהMEM_RESERVE רושם את הטווח והמאפיינים ב-VAD, MEM_COMMIT צורך Commit Total כדי להבטיח אחסון, ו-page fault בגישה הראשונה מקצה דף פיזי ומוסיף אותו ל-Working Set1. MEM_RESERVE2. MEM_COMMIT3. גישה ראשונה (Touch)רישום הטווח והמאפיינים ב-VADצריכת Commit Total (עדיין אין דף פיזי)Page faultקישור דף פיזי מאופס ל-PTEהוספה ל-Working Set והרצה חוזרת של ההוראה

איור 1: Reserve, Commit ו-Touch הם אירועים נפרדים. RAM פיזי נקשר רק בשלב האחרון, הגישה הראשונה.

2. שלושה מבני ניהול שעוקבים אחרי דף וירטואלי

כדי להבין את הדרך מכתובת וירטואלית ל-RAM, צריך להבחין בשלושת מבני הניהול ש-Windows מחזיק.

מבנה יחידה תפקיד
VAD טווח כתובות וירטואליות מנהל מה האזור, Reserve/Commit, protection והתאמת section
page table / PTE דף וירטואלי מייצג את התרגום הנוכחי לדף פיזי, או מצב שעדיין לא מומש
PFN database דף פיזי עוקב אחרי בעלות, הפניות ומצב של כל דף RAM

ה-VAD מחזיק מידע על טווח, ה-PTE על דף וירטואלי, ו-PFN database על דף פיזי. מטפל ה-page fault מצליב אותם כדי להחליט אם הגישה יכולה להמשיך.

שלושה מבני ניהול מכתובת וירטואלית ל-RAM פיזיכתובת וירטואלית מנוהלת על ידי VAD ברזולוציית טווח ועל ידי PTE ברזולוציית דף וירטואלי, ו-PFN database עוקב אחרי הדף הפיזי שהוא יעד התרגום של ה-PTEשיפוט Reserve/Commit והגנהתרגום תקףכתובת וירטואליתVAD (ניהול טווחים)PTE (ניהול דף וירטואלי)PFN database (ניהול דף פיזי)דף RAM פיזי

איור 2: שלושה מבנים ברזולוציות שונות. טיפול ב-fault מצליב VAD ו-PTE ואז מעדכן את צד ה-PFN.

כוכבי המאמר הזה הם ה-VAD וה-PTE. את PFN database נראה מצד הדף הפיזי בחלק 2.

3. Reserve, Commit ו-Touch הם אירועים נפרדים

3.1. Reserve — לתפוס כתובת

קודם כל, שומרים טווח רציף של 256MiB כתובות וירטואליות.

void* base = VirtualAlloc(
    nullptr,
    256ull * 1024 * 1024,
    MEM_RESERVE,
    PAGE_NOACCESS);

מה שקרה כאן הוא רק שכתובת נתפסה במרחב הווירטואלי של התהליך, כדי שהקצאות אחרות לא יוכלו להשתמש בטווח. MEM_RESERVE לא מקצה אחסון פיזי ב-RAM או ב-pagefile.1

לתהליך 64-bit יש מרחב וירטואלי עצום, ולכן זה מעשי לעשות קודם 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;

המעבד מנסה לתרגם את הכתובת הווירטואלית לכתובת פיזית, אבל ל-PTE עדיין אין תרגום תקף לדף פיזי. כאן מתרחש page fault.

memory manager שמקבל את השליטה קובע שזו «גישה ראשונה לדף פרטי ב-Commit שניתן לכתיבה», משיג דף פיזי מאופס, קושר אותו ל-PTE ומוסיף אותו ל-Working Set. אחר כך הוא מריץ שוב את הוראת הכתיבה שנכשלה.

מהאפליקציה זה נראה כמו השמה בלבד, אבל בפנים השליטה נכנסת ל-kernel באמצע ההשמה, מוקצה דף פיזי, וההרצה חוזרת לאותה הוראה.

4. ה-VAD — ניהול הטווחים של המרחב הווירטואלי

VAD הוא קיצור של Virtual Address Descriptor. Windows מנהל את טווחי הכתובות שבשימוש של תהליך כעץ של VAD. בפקודת !vad של WinDbg אפשר לבדוק VPN התחלה וסיום, Commit, מאפייני protection, Private/Mapped, את ה-Control Area ועוד.3

מידע מייצג ש-VAD רושם:

  • התחלה וסיום של טווח הכתובות
  • סוג כמו Private, Mapped או Image
  • מצב Reserve/Commit
  • protection כמו קריאה, כתיבה, הרצה ו-copy-on-write
  • התאמה לקובץ או ל-section
  • מאפיינים מיוחדים כמו Guard page

הסיבה לניהול לפי טווח היא יעילות. 256MiB הם 65,536 דפים ב-4KiB. במקום לבנות מראש מבנה ניהול מלא לכל דף, זול יותר להחזיק «הטווח הרציף הזה הוא שמירה אחת» ב-VAD ולממש דפים כשהם נעשים נחוצים.

4.1. מציאת VAD אינה מבטיחה התאוששות

«אם זה ב-VAD ה-fault נפתר; אם לא, מקבלים access violation» הוא הסבר כניסה נוח, אבל הוא מפשט יותר מדי. גם כשנמצא VAD, גישה רגילה לא יכולה להמשיך במקרים כמו:

  • Reserve בלבד, והדף היעד אינו ב-Commit
  • PAGE_NOACCESS
  • כתיבה לדף לקריאה בלבד
  • הרצת הוראה מדף שאינו ניתן להרצה
  • נגיעה ראשונה ב-Guard page
  • נגיעה מחוץ לטווח התקף של section

להפך, גם אם ה-PTE אינה תקפה, אם מצב התוכנה של ה-VAD וה-PTE מראה גישה לגיטימית, אפשר לפתור כ-demand-zero, שחזור Transition, page-in או CoW. מדויק יותר: לשפוט יחד VAD, PTE, protection וסוג גישה.

5. page table ו-TLB

ה-pointer שהאפליקציה מחזיקה הוא כתובת וירטואלית. כדי שהמעבד ייגש ל-RAM הוא חייב לתרגם מספר דף וירטואלי למספר דף פיזי. טבלת התרגום ההיררכית הזו היא page table, והרשומה בקצה היא ה-PTE (Page Table Entry).

PTE תקפה מחזיקה מושגית PFN, הגנת read/write/execute, הרשאת user mode, Accessed/Dirty ומידע דומה. פריסת הסיביות בפועל תלויה במעבד ובגרסת Windows.

ללכת ב-page table בכל פעם יהיה איטי מדי, לכן המעבד שומר תרגומים אחרונים ב-TLB (Translation Lookaside Buffer). תרגום כתובות מתקדם בסדר הזה.

  1. אם ב-TLB יש תרגום והגישה תואמת את ה-protection, משתמשים בתוצאה הזו.
  2. אם ב-TLB אין תרגום, המעבד הולך ב-page table.
  3. אם יש PTE תקפה וה-protection גם תואם, היא נרשמת ב-TLB וההרצה נמשכת.
  4. אם אין תרגום תקף, או שיש הפרת protection, השליטה ממשיכה לנקודת הכניסה של page fault. בדיקת ה-protection מתבצעת גם כשהתרגום הגיע מה-TLB.

כפי שמראה הזרימה הזו, TLB miss ו-page fault הם דברים שונים. אם הבעיה היחידה היא שב-TLB אין תרגום וה-PTE תקפה, קורה רק page-table walk. להפך, גם אם ב-TLB יש תרגום, הפרת protection כמו כתיבה לדף לקריאה בלבד או הרצת הוראה בדף שאינו ניתן להרצה ממשיכה לכניסת page fault. לכן כתיבה לדף CoW יכולה להיכשל גם כשהתרגום כבר ב-cache.

זרימת תרגום כתובות ונקודת הכניסה של page faultגם אם ב-TLB יש תרגום, אי-התאמת protection ממשיכה לנקודת הכניסה של page fault. אם ב-TLB אין תרגום הולכים ב-page table; PTE תקפה שגם תואמת protection נרשמת ב-TLB וההרצה נמשכתכןתואםהפרת protectionלאכןלא תקף או הפרת protectionגישה לזיכרוןהאם ב-TLB יש תרגום?האם הגישה תואמת את ה-protection?להמשיך עם התרגום הזהלנקודת הכניסה של page faultpage-table walkPTE תקפה וה-protection גם תואם?רישום ב-TLB והמשך (בלי fault)

איור 3: TLB miss אפשר לפתור ב-page-table walk. השליטה ממשיכה ל-page fault כשהתרגום אינו תקף או שיש הפרת protection, והפרת protection מתרחשת גם ב-TLB hit.

5.1. PTE לא תקפה אינה סתם ריק

גם PTE לא תקפה אינה ריקה. ממצב התוכנה של PTE לא תקפה Windows מבדיל מקרים כמו:

  • דף demand-zero שמעולם לא מומש
  • דף Transition שנשאר ב-RAM
  • דף משותף שמפנה ל-Prototype PTE
  • דף פרטי שנשמר ב-pagefile
  • הפרת protection או אזור לא תקף

עבודת המעבד היא רק להחליט «זה אינו תרגום תקף רגיל» ולמסור ל-kernel; memory manager מספק את המשמעות משם.

6. Page fault מההתחלה ועד הסוף

נעקוב אחרי כתיבה ראשונה לדף פרטי ב-Commit בשישה שלבים.

  1. המעבד מנסה לכתוב.
    הוא בודק את ה-TLB ואת ה-page table, אבל ל-PTE היעד אין PFN תקף.
  2. המעבד מרים page fault.
    הוא מעביר ל-kernel את הכתובת הווירטואלית שבה אירע ה-fault, סוג read/write/execute, user/kernel, והאם הבעיה היא תרגום חסר או הפרת protection.
  3. memory manager בודק את ה-VAD ואת ה-PTE.
    הוא מחליט אם הדף ב-Commit, אם ה-protection תואם, ואיזה מבין demand-zero, Transition, משותף, page-in, CoW או exception חל.
  4. אם זה demand-zero, משיגים דף פיזי מאופס.
    דף שנמסר זה עתה חייב להיות אפס כדי שנתונים של תהליך אחר לא ידלפו.
  5. מידע הניהול של PTE ו-PFN מתעדכן.
    PFN וה-protection נקבעים ב-PTE, הדף הפיזי נעשה Active, והוא מתווסף ל-Working Set של התהליך.
  6. ההוראה שנכשלה רצה שוב.
    כי ה-fault נפתר כרגיל, לא נמסרת exception במצב משתמש, והאפליקציה ממשיכה את ההשמה כרגיל.

אירועי ETW של page fault גם רושמים Transition, Demand Zero, Copy-on-Write, Guard Page, Hard Page Fault ו-Access Violation כסוגים נפרדים.4

אז page fault אינו מילה שפירושה «חריג» מההתחלה. זו נקודת הכניסה המשותפת לבקש ממערכת ההפעלה להחליט כשהמעבד לא יכול היה לתרגם בנתיב הרגיל.

התפצלות פתרונות page faultmemory manager שופט VAD, PTE, protection וסוג גישה ומפנה ל-demand-zero, חיבור מחדש של דף שעדיין ב-RAM, hard fault מ-backing store, copy-on-write, הודעת Guard page או exceptionגישה ראשונהעדיין ב-RAMנדרשת קריאת דיסקכתיבת CoWGuard pageלא ניתן לפתורמתרחש page faultשיפוט VAD, PTE, protection, סוגDemand-zero (soft)חיבור מחדש מ-Standby (soft)Hard fault (I/O לדיסק)העתקה והחלפת ה-PTEניקוי ה-guard והודעהexception (0xC0000005 וכו')

איור 4: faults שנכנסים מאותה נקודת כניסה מתפצלים לשישה סוגי תוצאה לפי השיפוט. פרטי Guard page בפרק 9.

7. Demand-zero —‏ soft fault שאינו קורא דיסק

Demand-zero הוא ה-soft fault המייצג שמתרחש כשנוגעים לראשונה בדף פרטי ב-Commit. גם תיעוד Working Set של Microsoft מפרט «התהליך מפנה בפעם הראשונה לדף וירטואלי שהוקצה» כדוגמה ל-soft fault.5

ל-demand-zero המאפיינים הבאים.

  • אין צורך לקרוא נתוני מקור מהדיסק
  • תוכן ההתחלה הוא אפס
  • נקשר דף פיזי זמין
  • Working Set ו-Page Fault Count המצטבר גדלים
  • הטיפול הזה לבדו אינו מגדיל Memory\\Pages Input/sec

לכן קפיצה ב-Page Faults/sec מיד אחרי ההפעלה אינה אומרת לבדה שה-storage הוא צוואר הבקבוק.

שווה גם לסדר את הפשרה של הקצאה lazy. אם עשיתם Commit ל-256MiB ובאמת משתמשים רק ב-8MiB, להשאיר את 248MiB הנותרים מחוץ ל-RAM סביר. בתמורה, הגישה הראשונה נושאת את עלות טיפול ה-fault. לעבודה רגישה ל-latency יש עיצוב שנוגע בכל דף לפני ההתחלה כדי לעשות prefault, אבל זו פשרה שמגדילה מראש את כמות ה-RAM resident.

8. Soft fault ו-hard fault

8.1. Soft fault

soft fault הוא fault שאפשר לפתור בלי read I/O ל-backing store. דוגמאות מייצגות:

  • Demand-zero
  • חיבור מחדש של דף שנשאר ב-Standby/Transition
  • חיבור דף משותף שנמצא ב-Working Set של תהליך אחר
  • חיבור דף שנטען מראש
  • copy-on-write שהדף המקורי שלו resident

עדיין יש עלות מעבד למעבר ל-kernel, נעילות, עדכוני PTE/PFN, עקביות TLB וכדומה, אבל אין המתנת storage.5

8.2. Hard fault

מצד שני, כשהדף הנחוץ אינו בשום מקום ב-RAM וחייבים לקרוא אותו מ-backing store, זו hard fault. מקור הקריאה אינו רק pagefile.

  • דף פרטי שנכתב ל-pagefile
  • קובץ שממופה לזיכרון
  • image של EXE או DLL
  • קובץ נתונים שמטמון הקבצים מפנה אליו

אירועי ETW HardFault כוללים FileObject, ReadOffset ו-ByteCount, כך שאפשר לעקוב אחרי מקור הקריאה בפועל.6

לכן Hard Fault = קריאה של pagefile.sys אינו נכון.

כשנדרשת קריאה מ-backing store, הבקשה נכנסת ל-I/O stack של Windows. זרימת IRP והנפקה/השלמה מכוסה ב«מעמקי I/O של Windows (חלק 1)», והצומת עם Cache Manager ב«מעמקי I/O של Windows (חלק 4)». אם הדף ב-RAM, memory manager יכול לחזור לבד; אם לא, הוא מנפיק I/O וממתין ל-thread שבו אירע ה-page fault עד ההשלמה.

9. Fault שלא ניתן לפתור הופך ל-exception

Fault שלאחר בדיקת VAD ו-PTE אי אפשר לפתור כהקצאה לגיטימית, page-in או CoW נמסר למצב משתמש כ-exception.

המקרה המייצג הוא STATUS_ACCESS_VIOLATION, קוד exception 0xC0000005. הוא מתרחש בקריאה, כתיבה או הרצה של כתובת לא תקפה; פרמטר ה-exception הראשון מציין את סוג הגישה והשני את הכתובת המפרה.7

תבניות טיפוסיות:

  • קריאת NULL, כתובת ששוחררה או כתובת מחוץ למערך
  • כתיבה לדף לקריאה בלבד
  • הרצת הוראה מדף ש-DEP/NX הפך ללא ניתן להרצה
  • נגיעה בטווח שמור שאינו ב-Commit

ל-PAGE_GUARD משמעות קצת אחרת. זו הודעה חד-פעמית על גישה: היא מרים STATUS_GUARD_PAGE_VIOLATION ומשמשת לדברים כמו גידול stack.8

הקצאה lazy רגילה, page-in, CoW, הודעת guard ו-access violation סופית מתכנסות, מנקודת המבט של המעבד, באותה נקודת כניסה של page fault. מה שמחליט את התוצאה הוא השילוב של VAD, PTE, protection וסוג גישה.

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");
}

משורת הפקודה Native Tools x64 של Visual Studio אפשר לבנות בפקודה הבאה.

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. הפרדת soft ו-hard ב-PerfMon

ב-PerfMon שימו את המונים הבאים על אותו ציר זמן.

  • Process(<target>)\\Page Faults/sec
  • Memory\\Pages Input/sec
  • Memory\\Page Reads/sec
  • Memory\\Available MBytes
  • Process(<target>)\\Working Set - Private
  • Process(<target>)\\Private Bytes

Process\\Page Faults/sec כולל גם soft וגם hard. Memory\\Pages Input/sec, לעומת זאת, הוא מספר הדפים שנקראו מהדיסק כדי לפתור hard fault.10

בשלב Touch של התוכנית הזו Page Faults/sec אמור לקפוץ בעוד Pages Input/sec לא אמור לעלות הרבה. דפים חדשים ב-Commit ממומשים ב-demand-zero, לכן אין צורך לקרוא נתוני מקור מהדיסק.

כשכמה תהליכים חולקים אותו שם, מספרי PerfMon כמו process#1 יכולים להשתנות בין הפעלות מחדש. הצליבו מול מונה שמציג PID, או זהו לפי PID עם Process V2 או ETW/WPA.

11. שלוש קריאות שגויות להימנע מהן בפועל

11.1. «ה-Commit עלה, אז זו דליפת RAM»

Commit הוא הכמות המובטחת של תוכן לשמור; דפים שלא נגעו בהם אולי אינם resident ב-RAM. כדי לשפוט דליפה, הסתכלו על סדרת הזמן של Private Bytes, פירוק ההקצאות, והאם המספר חוזר לקו בסיס אחרי סיום העיבוד.

11.2. «Page Faults/sec גבוה, אז הדיסק איטי»

soft fault אינו כולל I/O לדיסק. הפרידו Page Faults/sec, Pages Input/sec והמתנת storage, ואם צריך עקבו אחרי קובץ המקור וה-stack עם אירועי ETW HardFault.

11.3. «ריקון ה-Working Set יתקן את הדליפה»

הוצאת דף מ-Working Set אינה משחררת Commit או בעלות. הדף עובר ל-Standby או Modified ואחר כך חוזר ב-fault. תיקון דליפה דורש שהמקצה יבצע VirtualFree, שחרור heap, הרס אובייקט וכדומה.

לאן הולך הדף הפיזי שהוסר הוא מה שאנחנו עוקבים אחריו בחלק 2.

12. סיכום

  • MEM_RESERVE תופס טווח כתובות וירטואליות אבל אינו מקצה אזור פיזי ב-RAM או ב-pagefile.1
  • MEM_COMMIT צורך Commit ומבטיח שאפשר יהיה לשמור את התוכן בעתיד, אבל דף פיזי רגיל אינו מוקצה עד הגישה הראשונה.12
  • ה-VAD מנהל טווחים, ה-PTE מנהל דף וירטואלי, ו-PFN database מנהל דף פיזי.
  • TLB miss אינו page fault. אם ה-PTE תקפה, page-table walk לבדו פותר אותה.
  • Demand-zero, שחזור Transition וחיבור דף משותף הם soft fault שאפשר לפתור בלי I/O לדיסק.5
  • אם נדרשת קריאה מ-pagefile, DLL, EXE או קובץ ממופה, זו hard fault.6
  • אם בדיקת VAD, PTE ו-protection אינה יכולה לפתור את ה-fault, מקבלים exception כמו 0xC0000005.7
  • לשיפוט ביצועים אל תסתכלו על Page Faults/sec לבד; הסתכלו על Pages Input/sec, Available, Working Set, Private Bytes והמתנת storage על אותו ציר זמן.

המשך בחלק 2, «מחזור החיים של דף פיזי: חמש רשימות והאמת על pagefile».

אחרי שהבטחת ה-Commit הפכה לדף פיזי, נעקוב לאן הולך הדף הזה כשהוא יוצא מ-Working Set, מ-PFN database ומרשימות הדפים.

מאמרים קשורים

תחומי ייעוץ קשורים

KomuraSoft LLC מטפלת בחקירות שימוש בזיכרון של יישומי Windows, access violation, עיכובי הפעלה, paging ופגמים בקוד native. זה חלק מ-Windows Custom Software Development.

קישורים

  1. Microsoft Learn, VirtualAlloc function. על כך ש-MEM_RESERVE שומר טווח כתובות וירטואליות בלי להקצות אחסון פיזי; ש-MEM_COMMIT גובה commit charge מול הזיכרון ו-pagefile של המערכת; שתוכן ההתחלה של דף ב-Commit הוא אפס; ושהדף הפיזי בפועל אינו מוקצה עד שניגשים אליו. ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  2. Microsoft Learn, PERFORMANCE_INFORMATION structure. על כך ש-CommitTotal הוא מספר דפי ה-Commit הנוכחי של המערכת, ו-CommitLimit הוא הגבול העליון שאפשר לעשות לו Commit בלי להרחיב את ה-pagefile. ↩ ↩2

  3. Microsoft Learn, !vad (WinDbg). על כך ש-!vad מציג את עץ ה-VAD ומאפשר לבדוק VPN התחלה וסיום, Commit, Mapped/Private, מאפייני protection, את ה-Control Area ועוד. ↩

  4. Microsoft Learn, PageFault_TypeGroup1 class. על כך ש-ETW מבדיל ורושם Transition Fault, Demand Zero Fault, Copy-on-Write, Guard Page Fault, Hard Page Fault ו-Access Violation. ↩

  5. Microsoft Learn, Working Set. על כך ש-soft fault ניתן לפתרון בלי לגשת ל-backing store, ומתרחש מ-Working Set של תהליך אחר, מ-Transition, מ-demand-zero בהפניה ראשונה וכדומה. ↩ ↩2 ↩3

  6. Microsoft Learn, PageFault_HardFault class. על כך שאירוע HardFault כולל FileObject, ReadOffset, ByteCount, VirtualAddress ומזהה thread, כך שאפשר לעקוב אחרי מקור הקריאה. ↩ ↩2

  7. Microsoft Learn, Access Violation C0000005. על כך ש-0xC0000005 מתרחש בקריאה, כתיבה או הרצה של כתובת זיכרון לא תקפה, ופרמטרי ה-exception מציינים את סוג הגישה ואת הכתובת המפרה. ↩ ↩2

  8. Microsoft Learn, Creating Guard Pages. על כך ש-PAGE_GUARD מספק הודעה חד-פעמית על גישה לדף ומרים STATUS_GUARD_PAGE_VIOLATION. ↩

  9. Microsoft Learn, VMMap - Sysinternals. על כך ש-VMMap מפרק זיכרון וירטואלי ב-Commit לפי סוג ומציג את ה-Working Set של כל סוג ומפת כתובות מפורטת. ↩

  10. Microsoft Learn, Performance Analysis of Logs (PAL) Tool. על כך ש-Memory\\Pages Input/sec הוא מספר הדפים שנקראו מהדיסק כדי לפתור hard fault. ↩

מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.

העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.

המאמר קשור ישירות לשירותים הבאים.

שאלות נפוצות

שאלות נפוצות בפניות בנושא המאמר.

האם MEM_COMMIT ב-VirtualAlloc מקצה RAM מיד?
בזיכרון פרטי רגיל, Commit צורך מרווח commit של המערכת, אבל הדף הפיזי המתאים לא מוקצה עד הגישה הראשונה. דף שנכתב אליו בפעם הראשונה מקבל דף פיזי בזמן טיפול ב-demand-zero fault.
האם page fault אומר שיש תקלה או בעיית ביצועים?
לא. soft fault בלי I/O לדיסק — demand-zero או החזרה מ-Standby — זו התנהגות רגילה. לשיפוט ביצועים אל תסתכלו רק על Page Faults/sec; צרפו Pages Input/sec, המתנת storage ו-Available MBytes.
האם TLB miss ו-page fault הם אותו דבר?
לא. גם אם ב-TLB אין תרגום, PTE תקפה ב-page table אומרת שהמעבד רק הולך בטבלה ורושם את התרגום מחדש. הכניסה ל-page fault היא כשה-PTE לא תקפה, או כשיש הפרת protection.
אם טווח הכתובות נמצא ב-VAD, לא יכולה להיות access violation?
לא בהכרח. מעבר לקיום VAD, memory manager בודק Reserve מול Commit, הגנת read/write/execute, Guard page, מצב ה-PTE ועוד. אם אי אפשר לפתור את ה-fault, מגיעה exception כמו 0xC0000005.
האם Page Faults/sec גבוה אומר שחסר RAM?
אי אפשר לדעת מזה לבד. Page Faults/sec כולל גם הרבה soft fault. צריך להצליב על אותו ציר זמן עם Memory\Pages Input/sec, Memory\Page Reads/sec, Available MBytes וזמן המתנת דיסק.

פרופיל הכותב

עמוד היכרות עם כותב המאמר.

Go Komura

מנהל KomuraSoft LLC

מתמחה בפיתוח תוכנה עבור Windows, ייעוץ טכני וחקירת תקלות, בעיקר בפרויקטים עם מערכות קיימות ובאגים שקשה לשחזר.

קישורים ציבוריים

חזרה לבלוג