מעמקי ה-I/O ב-Windows (פרק 5) — המבנה הפנימי של NTFS: מערכת קבצים מתוך MFT
· עודכן בתאריך: · Go Komura · Windows, NTFS, I/O, file system, MFT, kernel, .NET, troubleshooting
היסטוריית עדכונים (גרסה ראשונה, פורסמה בתאריך 29 Jul 2026)
- פרסום ראשון
לצטט את המאמר הזה(DOI: 10.5281/zenodo.22175348)
מאמר זה מאוחסן בארכיון Zenodo. להלן גם ה-DOI שתמיד מפנה לגרסה האחרונה וגם ה-DOI המקובע לגרסה שאתם קוראים.
Go Komura (2026). מעמקי ה-I/O ב-Windows (פרק 5) — המבנה הפנימי של NTFS: מערכת קבצים מתוך MFT. KomuraSoft LLC. https://doi.org/10.5281/zenodo.22175348 https://comcomponent.com/he/blog/ntfs-internals-mft-structure/
- DOI (הגרסה האחרונה)
- 10.5281/zenodo.22175348
- DOI (הגרסה הזו)
- 10.5281/zenodo.22175349
בארבעת הפרקים עד כאן עקבנו אחרי איך בקשת I/O זורמת (פרקים 1–3) ואיך Cache Manager מקבל אותה (פרק 4). בסוף הבקשה מגיעה למערכת הקבצים. הפעם תור הדוגמה המובילה שלה, NTFS.
נקודת המבט משתנה. עד עכשיו הסיפור היה דינמי: זרימת בקשה. הפעם זה סיפור סטטי על איך נתונים מסודרים בדיסק. הזהות האמיתית של Zone.Identifier הבלתי נראה שמצורף לקובץ שהורד. למה העתקת עשרת אלפים קבצים קטנים איטית בהרבה מהעתקת קובץ אחד באותו גודל כולל. עד כמה באמת נכון ש”NTFS עם journal, אז זה בטוח”. הכול ניתן להסביר מהמבנה הזה.
זה פרק 5 בסדרה “מעמקי ה-I/O ב-Windows”.
רקע לקריאה של הפרק הזה: אם יש לכם את היסודות של IRP ומחסנית ההתקנים מפרק 1, הקריאה חלקה יותר. בכל זאת, כדי שאפשר יהיה לקרוא גם לבד, מגדירים קודם את המונחים מפרקים קודמים שמופיעים בגוף.
| מונח | בשורה אחת | פירוט |
|---|---|---|
| IRP (I/O Request Packet) | “תלוש בקשת I/O” שקריאת API כמו ReadFile הופכת אליו בתוך ה-kernel. נהגים מקבלים את התלוש ומעבדים |
פרק 1 |
| I/O Manager ומחסנית התקנים | רכיב ה-kernel שבונה IRP ומעביר אותו לפי הסדר לנהגים שערמו עד ההתקן היעד, והערמה עצמה | פרק 1 |
| Cache Manager | רכיב שמחזיק תוכן קובץ בזיכרון, וכותב אחר כך לדיסק את מה ש-WriteFile כתב. הסיבה לכך ש”כתבתי עכשיו” לא אומר “כבר בדיסק” |
פרק 4 |
טבלה 1: מונחים מפרקים קודמים שהפרק הזה מניח
עוד דבר מפרק 1: שני השלבים cleanup (כשנסגר ה-handle האחרון) ו-close (כשכל הייחוסים בתוך ה-kernel נעלמו). נשתמש בהם בהסבר מחיקת קובץ בפרק 4.
1. קודם המסקנה
- המרכז של NTFS הוא MFT (Master File Table). כל הקבצים מנוהלים כרשומות בפנקס שבתוך MFT, וכל המידע על קובץ נמצא “בתוך רשומת MFT” או “באזור מחוץ ל-MFT שהרשומה מצביעה עליו” (פרק 2).1
- הישות של קובץ היא אוסף attributes. קובץ קטן נכנס כולו, כולל הנתונים, לתוך רשומת MFT (resident); קובץ גדול מחזיק רק הפניה לרצף clusters (non-resident). האיטיות בטיפול בהמון קבצים קטנים יוצאת מכאן (פרק 2).1
- אפשר להחזיק כמה data streams. הנתונים הרגילים הם “stream בלי שם”, ואפשר stream נוסף בסינטקס
filename:streamname. זו הזהות של Zone.Identifier (Mark of the Web) (פרק 3).2 - גם שם הוא attribute. כמה שמות על אותה רשומה הם hard link. גם שם מקוצר 8.3 יושב שם כשם נוסף (פרק 4).34
- reparse point הוא המנגנון הרשמי ל”פתחתי, ואז זה מקום אחר”. symbolic link, junction, ו-Files On-Demand של OneDrive כולם יישומים של נתונים עם tag (פרק 5).56
- יש שני journals.
$LogFileהוא לשחזור עקביות metadata (journal מקדים כדי לא להישבר), USN Journal הוא לתיעוד היסטוריית שינויים (פנקס של מה השתנה). התפקידים שונים לגמרי (פרק 6).78 - “גודל” ו”גודל בדיסק” הם שני דברים. sparse ודחיסה מייצרים את הפער. גם הרקע ל”קובץ דחוס לא נהיה אסינכרוני” מפרק 2 נמצא כאן (פרק 7).910
ב-diagram, solid line מציינת relation שתמיד מתקיים ו-dashed line מציינת relation מותנה (התנאים מופיעים בהסבר של כל relation ב-detail page). הרשימה המלאה של ה-relations (סה”כ 35, כולל evidence ו-certainty) וההגדרות של ה-concepts המרכזיים נמצאות ב-detail page של ה-knowledge map (ביפנית). Data: JSON-LD / Turtle
2. הכול הוא רשומת MFT
2.1. הפנקס של ה-volume
כשמפרמטים volume של NTFS, נוצרים MFT (master file table) וסדרת קובצי metadata שמתחילים ב-$. ב-MFT יש לפחות רשומה אחת לכל קובץ ב-volume, וגם רשומה של MFT עצמה כלולה.1
flowchart TB
subgraph VOL["volume של NTFS"]
MFT["$MFT: Master File Table<br/>פנקס הרשומות של כל הקבצים (גם היא עצמה רשומה)"]
LOG["$LogFile: journal טרנזקציות של<br/>פעולות metadata (פרק 6)"]
BITMAP["$Bitmap: מצב השימוש ב-clusters"]
OTH["$Boot / $Secure / $UpCase וכו'<br/>קובצי metadata אחרים"]
DATA["אזור נתוני משתמש<br/>(איפה יושבים נתונים non-resident)"]
end
MFT -->|"הרשומה מצביעה על המיקום"| DATA
איור 1: מבנה volume של NTFS. העיצוב הוא שגם מידע הניהול של מערכת הקבצים עצמה יושב כקבצים
מידע על קובץ (גודל, timestamps, הרשאות גישה, ואפילו תוכן הנתונים) נשמר בתוך רשומת MFT, או באזור מחוץ ל-MFT שהרשומה מתארת את מיקומו.1 כשמוחקים קובץ הרשומה מסומנת כפנויה וחוזרת לשימוש, אבל MFT עצמה לא מצטמצמת. בנוסף, כדי לשמור על MFT רציף שמור MFT zone; כשה-volume מתמלא מתחילה פרגמנטציה של MFT. גם סיפור תוחלת החיים הזה כתוב בתיעוד הרשמי.1
2.2. קובץ = אוסף attributes, resident ו-non-resident
תוכן file record הוא רשימת attributes. מידע סטנדרטי (timestamps וכו’), שם קובץ, אבטחה, ונתונים. כאן יש פיצול חשוב.
flowchart TB
subgraph REC["file record של MFT (פנקס של קובץ אחד)"]
STD["attribute מידע סטנדרטי<br/>timestamps ו-flags"]
FN["attribute שם קובץ<br/>(אפשר כמה: פרק 4)"]
DATA["attribute נתונים"]
end
Q{"הנתונים קטנים?"}
RES["resident<br/>גוף הנתונים נכנס לתוך הרשומה<br/>קריאה מסתיימת בגישה ל-MFT בלבד"]
NONRES["non-resident<br/>ברשומה רק הפניה לרצף clusters<br/>הנתונים עצמם באזור נתוני המשתמש"]
DATA --> Q
Q -->|"עד כמה מאות בתים"| RES
Q -->|"מעבר לזה"| NONRES
איור 2: file record הוא אוסף attributes. אם הנתונים קטנים הם resident בתוך הרשומה
קל יותר לראות איך תוכן אותה רשומה משתנה בין resident ל-non-resident אם שמים אותם זה מול זה.
flowchart LR
subgraph RES2["resident: קובץ קטן"]
RA["file record של MFT (אורך קבוע)<br/>מידע סטנדרטי / שם / אבטחה<br/>─────────────<br/>attribute נתונים = התוכן עצמו<br/>ערך הגדרה=1 נכנס לכאן ישירות"]
RB["אין מקום נפרד בדיסק<br/>קריאה מסתיימת בגישה ל-MFT בלבד"]
RA --> RB
end
subgraph NON2["non-resident: קובץ גדול"]
NA["file record של MFT (אורך קבוע)<br/>מידע סטנדרטי / שם / אבטחה<br/>─────────────<br/>attribute נתונים = טבלת data runs<br/>רצף של מאיפה כמה clusters"]
NB["אזור נתוני משתמש<br/>run 1: clusters רצופים"]
NC["אזור נתוני משתמש<br/>run 2: clusters רצופים במקום אחר"]
NA -->|"מצביע על מיקום"| NB
NA -->|"מצביע על מיקום"| NC
end
איור 3: resident מול non-resident. ב-non-resident הרשומה מחזיקה רק טבלה של איפה יש כמה נתונים אמיתיים (data runs)
ככל שמספר ה-data runs גדל, קריאת קובץ אחד דורשת לעבור בין אזורים מפוזרים. זו הזהות של הפרגמנטציה בסעיף הבא.
מהמבנה הזה אפשר להסביר כמה תופעות שפוגשים בשטח.
- למה העתקת 10,000 קבצים קטנים איטית. לכל קובץ יש יצירת רשומת MFT, רישום שם, והגדרת אבטחה. עבודת הפנקס שולטת יותר מהעברת הנתונים עצמה (וכל אחת מהן הופכת גם ליעד בדיקה של filters בפרק 6).
- הזהות של פרגמנטציה. נתונים non-resident נרשמים כ”רצף של מקטעי clusters רצופים (runs)”. אם אין אזור רצוף, מספר ה-runs גדל, ומספר ה-seeks לקריאה גדל. זה פרגמנטציה. את סידור ה-runs אפשר לראות ב-
fsutil file layout. - גם תיקייה לא מיוחדת. directory הוא “קובץ שמחזיק אינדקס משם קובץ למספר רשומת MFT”. על הפנקס, הכול יושב על אותו מנגנון.
3. נתונים הם רק stream אחד
3.1. קובץ אחד, כמה רצפי בתים
ב-NTFS קובץ אחד יכול להחזיק כמה data streams. מה שקוראים וכותבים בדרך כלל ב-ReadFile/WriteFile הוא default stream בלי שם, ובסינטקס filename:streamname אפשר ליצור alternate data stream (ADS).2
flowchart LR
subgraph F["הקובץ report.docx (רשומת MFT אחת)"]
D0["default stream (בלי שם)<br/>= התוכן שרואים בדרך כלל"]
D1[":Zone.Identifier<br/>מידע מקור (Mark of the Web)"]
D2[":שם כלשהו<br/>מידע נוסף של האפליקציה"]
end
איור 4: כמה data streams. תצוגת הגודל ב-Explorer מראה רק את ה-default stream
ה-ADS הכי מוכר הוא Zone.Identifier. לקובץ שהורד בדפדפן נרשם ב-stream הזה המקור (למשל מהאינטרנט), וזה הופך לחומר לשיפוט של “Windows protected your PC” ב-SmartScreen ושל Protected View ב-Office. הצד הגלוי של המנגנון מכוסה ב”למה מופיע Windows protected your PC”. מאחור, זו פשוט NTFS stream.
3.2. מלכודות שמפתחים דורכים עליהן
- לא רואים. לא בגודל ב-Explorer ולא ב-
dir. בודקים ב-dir /rאו ב-streamsשל Sysinternals.11 - לא עוברים. ADS הוא יכולת של NTFS, ולכן בהעתקה ל-USB ב-FAT או דרך אחסון ענן הוא נוטה להיעלם. “האזהרה על הורדה נעלמה אחרי שהעתקתי” זה זה.
- אפשר לפתוח גם מהאפליקציה שלכם. מספיק לכלול נקודתיים בנתיב, כמו
CreateFile("data.txt:meta", ...).2 זה נוח, אבל אתם מקבלים גם את תכונת “לא עובר”, ולכן זה לא מקום לגוף נתונים עסקיים.
4. גם שם הוא attribute: hard link ושמות 8.3
4.1. hard link: כמה שמות לאותה רשומה
באיור 2 כתבנו שאפשר כמה attributes של שם קובץ. בתוך אותו volume, כמה נתיבים מצביעים על קובץ אחד — זה hard link (CreateHardLink / mklink /H).3
flowchart TB
subgraph DIR1["אינדקס של C:\\app\\"]
E1["config.json → רשומה 1234"]
end
subgraph DIR2["אינדקס של C:\\backup\\"]
E2["config-link.json → רשומה 1234"]
end
REC["רשומת MFT 1234<br/>גוף הנתונים (או הפניה ל-runs)<br/>מספר קישורים: 2"]
E1 --> REC
E2 --> REC
איור 5: hard link. האינדקס של ה-directory מצביע על אותה רשומת MFT, ושניהם “אמיתיים”
שינוי דרך כל שם הוא אותו קובץ, ולכן התוכן מתיישר מיד.3 ומשמעות “מחיקה” משתנה: DeleteFile הוא “להוריד שם אחד”, והישות נעלמת רק כשהשם האחרון ירד, handles פתוחים נסגרו, וגם ייחוסים בתוך ה-kernel כמו section של memory map נעלמו כולם. שני השלבים מפרק 1, cleanup (ה-handle האחרון) ו-close (הייחוס האחרון), פועלים גם על תוחלת החיים של מחיקה. לתצוגת attributes יש גם מוזרות רשמית: שינוי attribute דרך קישור אחד לא בהכרח מעדכן את התצוגה הנראית דרך קישור אחר.3
4.2. שם 8.3: עוד שם מוסתר
בגלל תאימות היסטורית, NTFS יכולה לייצר אוטומטית שם מקוצר בפורמט 8.3 כמו REPORT~1.DOC לשם ארוך. גם זה יושב ברשומה כשם נוסף. בתיקייה עם הרבה קבצים, יצירת שם מקוצר והימנעות מהתנגשות עולות; אפשר לבטל יצירה ולהסיר שמות קיימים ב-fsutil 8dot3name (אם אפליקציה ישנה רשמה נתיב Registry בשם מקוצר זה נשבר, ולכן יש גם יכולת בדיקה לפני strip, וזה נקודה מעשית).4
חשוב: האם יש שם מקוצר תלוי בסביבה. ברירת המחדל נקבעת בערך Registry NtfsDisable8dot3NameCreation: 0 (מייצרים בכל ה-volumes), 1 (לא מייצרים באף volume), 2 (הגדרה לפי volume), 3 (לא מייצרים מחוץ ל-volume של המערכת).4 אם בוחרים 2 אפשר לעבור לפי volume, ולכן “ב-Windows תמיד יש שם מקוצר כמו PROGRA~1” לא נכון. לפני שכותבים קוד או נוהל שתלויים בשם מקוצר, בדקו את המצב הנוכחי ב-fsutil 8dot3name query C: (בלי volume זה ברירת המחדל המשותפת לכל ה-volumes).
מלכודות סביב נתיב ושם (MAX_PATH, שמות שמורים, נקודה בסוף) מכוסות ב”MAX_PATH ומלכודות של נתיבים ושמות קבצים ב-Windows”. אם מחברים את פתרון השמות מפרק 1 (Object Manager) לפרק הזה (שמות בתוך מערכת הקבצים), מתקבלת התמונה המלאה של “שם” ב-Windows.
5. reparse point: המנגנון של “פתחתי, ואז זה מקום אחר”
אפשר לשים reparse point על קובץ או directory. הישות היא attribute של “tag + נתונים שהמשתמש מגדיר”. כשמערכת הקבצים פותחת קובץ עם reparse point, הטיפול נחטף לפי ה-tag: filter driver שמבין את ה-tag לוקח את הטיפול, או שב-tag של החלפת שם פותרים מחדש לפי נתיב היעד.5
sequenceDiagram
participant App as אפליקציה
participant IOM as I/O Manager
participant FS as NTFS
App->>IOM: CreateFile("C:\\data\\link.txt")
IOM->>FS: IRP_MJ_CREATE (העולם של פרק 1)
Note over FS: מצא reparse point ביעד<br/>מחזיר tag ונתונים
alt symbolic link / junction (החלפת שם)
FS-->>IOM: הנתיב האמיתי הוא כאן
IOM->>FS: פותר מחדש לפי נתיב היעד
else tag שמנוהל בידי filter (קובץ ענן וכו')
Note over FS: filter שמבין את ה-tag<br/>לוקח את הטיפול (פרק 6)
end
איור 6: פתרון reparse point. זה hook רשמי שמתערב בפעולת פתיחה
על המנגנון האחד הזה יושבות יכולות מוכרות.
- symbolic link (
mklink): שלט שמחזיק נתיב יעד. יכול להצביע גם ל-volume אחר או ל-UNC.6 - junction / mount point: מנגנון ותיק שמחבר directory למיקום ב-volume מקומי אחר.3
- Files On-Demand של OneDrive: קובץ בלי נתונים אמיתיים אצלכם מיוצג כ-reparse point, וברגע הפתיחה filter מוריד ומגיש את התוכן. זו הסיבה ל”רואים ב-Explorer, אבל בפתיחה רצה תקשורת” (מנגנון ה-filter עצמו בפרק 6).
נקודת זהירות אחת בפועל. “מה שמעבר לנתיב לא בהכרח המקום המקומי הזה”. כלי שעובר עץ רקורסיבית נכנס ללולאה ב-junction, סיכום גודל נספר פעמיים, גיבוי גורם להמון materialization בענן. קוד שלא מכיר reparse points דורך על אלה. נקודת הכניסה היא לבדוק את ה-attribute FILE_ATTRIBUTE_REPARSE_POINT ב-FindFirstFile.5
6. שני journals: $LogFile ו-USN
אומרים הרבה ש”NTFS היא journaling file system”, אבל ל-NTFS יש שני journals עם תפקידים שונים. אם מערבבים, קוראים לא נכון את מה שמובטח.
flowchart TB
subgraph J1["$LogFile: journal מקדים (כדי לא להישבר)"]
A1["לפני פעולת metadata (עדכון רשומה, שינוי שם וכו')<br/>רושמים ל-log"]
A2["אחרי כשל מערכת, באתחול הבא<br/>מריצים את ה-log ומשחזרים עקביות מבנה"]
A1 --> A2
end
subgraph J2["USN Journal: היסטוריית שינויים (כדי לדעת מה השתנה)"]
B1["בכל שינוי לקובץ / directory<br/>רושמים תוכן השינוי ושם"]
B2["גיבוי, אינדקס חיפוש וכלי סנכרון<br/>יודעים מה השתנה מאז הפעם הקודמת בלי סריקה מלאה"]
B1 --> B2
end
איור 7: שני journals. $LogFile כדי לא להישבר, USN כדי לדעת מה השתנה
ההבדל בטבלה:
| זווית | $LogFile (transaction log) |
USN Journal (change journal) |
|---|---|---|
| מטרה | אחרי כשל להחזיר את מבנה מערכת הקבצים למצב עקבי7 | לדעת אחר כך “מה השתנה מאז הפעם הקודמת”8 |
| מה נרשם | journal מקדים של פעולות metadata (עדכון רשומה, שינוי שם וכו’). תוכן הקובץ לא ביעד | בכל שינוי, תוכן השינוי ושם הקובץ / directory היעד8 |
| מי משתמש | NTFS עצמה. לשחזור אוטומטי ב-mount הבא | אפליקציות כמו גיבוי, אינדקס חיפוש, כלי סנכרון |
| כמה רחוק אפשר לחזור | רק מה שצריך לשחזור. גודל קבוע שממחזרים, ולכן לא מתאים להיסטוריה ארוכה | כשעוברים את MaximumSize, ברשומות ישנות נגזרות ב-checkpoint. טווח החזרה תלוי בהגדרת הגודל ובכמות העדכונים ב-volume12 |
| איך מסתכלים | אין אמצעי רשמי לקרוא את התוכן (אפשר לבדוק גודל ב-chkdsk /L) |
מצב ב-fsutil usn queryjournal, תוכן ב-fsutil usn readjournal. מתוכנית: FSCTL_QUERY_USN_JOURNAL / FSCTL_READ_USN_JOURNAL12 |
| אפשר לעצור | אי אפשר (חלק מ-NTFS) | admin יכול למחוק / לכבות. אבל זה כופה סריקה מלאה על שירותים שמשתמשים בזה, וההשפעה גדולה12 |
טבלה 2: שני journals זה מול זה
$LogFile(transaction log) הוא journal מקדים של פעולות metadata. גם אחרי כשל מערכת, NTFS משחזרת עקביות מערכת הקבצים אוטומטית מה-log וממידע checkpoint באתחול הבא.7 מה שמוגן כאן הוא מבנה. כמו בפרק 4, תוכן נתונים dirty במטמון יכול להיעלם בניתוק חשמל. הקריאה הנכונה: “ה-volume לא נשבר. אבל הכתיבה האחרונה יכולה להיעלם”.- USN Journal (change journal) הוא פנקס שרושם בכל שינוי לקובץ או directory ב-volume את תוכן השינוי ושם היעד.8 גיבוי ו-indexer אוספים רק מה שהשתנה מאז הפעם הקודמת, בלי סריקה מלאה, וזה משמש גם כדי להימנע מבניית אינדקס מחדש אחרי כשל.8 בפועל שווה לזכור שאפשר להשתמש ב-
fsutil usn readjournalכפנקס התאמה לחקירה של החמצות ב-FileSystemWatcher(“מדריך מעשי ל-FileSystemWatcher”).
7. sparse ודחיסה: יש שני “גדלים”
ב-NTFS, האורך הלוגי של קובץ והאזור שבאמת הוקצה מנוהלים בנפרד. במסך המאפיינים זה “גודל” ו”גודל בדיסק”. שני נציגים מייצרים את הפער.
קובץ sparse לא מקצה אזור אמיתי לטווחים שנמשכים כאפסים, ומנהל אותם כחורים.9 קובץ דיסק וירטואלי בגודל לוגי 42GB שעל הדיסק תופס 500MB קורה באופן רגיל. קריאת חור מחזירה אפסים; כתיבה מקצה רק את החלק הזה.
flowchart LR
subgraph L["קובץ לוגי (גודל: 1GB)"]
R1["נתונים 10MB"]
H1["חור (אפסים) 500MB"]
R2["נתונים 5MB"]
H2["חור (אפסים) השאר"]
end
subgraph P["הקצאה בדיסק (15MB + מידע ניהול)"]
A1["run: הישות של R1"]
A2["run: הישות של R2"]
end
R1 --> A1
R2 --> A2
איור 8: קובץ sparse. לחורים אין הקצאה, והגודל הלוגי נפרד מהגודל בדיסק
דחיסת NTFS דוחסת ומאחסנת לפי יחידת דחיסה.10 זה שקוף ונוח, אבל גם העלות שקופה: בכל קריאה וכתיבה רצים decompress ודחיסה מחדש, ופרגמנטציה מתקדמת בקלות. וכמו בפרק 2 פרק 5, גישה לקובץ דחוס לא נהיית אסינכרונית (מערכת הקבצים ממירה לסינכרוני). זה אחד המקומות לחשוד כש”הפכתי ל-I/O אסינכרוני והקובץ לא ממהר”.
את הגודל האמיתי לפי הקצאה לוקחים ב-GetCompressedFileSize. בחקירה ש“סכום גדלי הקבצים” לא מתאים ל”שימוש בדיסק”, חושדים לפי הסדר בארבעה: sparse, דחיסה, ADS (פרק 3), ועיגול ל-cluster.
8. לראות בעצמכם
גם הפעם אפשר לצפות בהכול ב-Windows אצלכם (חלק דורש הרשאות admin). לכל פקודה מצורף איפה להסתכל ומה זה אומר, כדי שתוכלו לשפוט לבד אם התוצאה נכונה.
:: לראות alternate data streams
dir /r C:\Users\%USERNAME%\Downloads
איפה להסתכל: מתחת לשורת קובץ רגילה מופיעות שורות מוזחות בצורה filename:Zone.Identifier:$DATA עם אורך. אם יש שורה כזו, לקובץ יש Mark of the Web (פרק 3). לקובץ שהורד בדפדפן זה מצורף; לקובץ שיצרתם בעצמכם לא. אם מריצים בשני המקומות ומשווים, קיום ADS מתבהר.
:: לראות פריסה (runs) ו-attributes על MFT
fsutil file layout C:\path\to\file.dat
:: רק extents (תת-פקודה שמופיעה בתיעוד הרשמי)
fsutil file queryextents C:\path\to\file.dat
איפה להסתכל: layout מציג לכל stream גודל, גודל מוקצה, ואם non-resident גם רשימת extents (זוגות VCN, LCN, מספר clusters). קובץ קטן מאוד בלי שורות extent הוא resident (סעיף 2.2); כמה שורות אומרות פרגמנטציה. הדרך הקצרה ביותר להרגיש resident / non-resident היא להריץ על קובץ טקסט של כמה בתים ועל קובץ של מאות MB ולהשוות.
:: הגדרת יצירת שם מקוצר 8.3, ושמות מקוצרים קיימים
fsutil 8dot3name query C:
dir /x
איפה להסתכל: query מחזיר אם יצירת שם מקוצר דלוקה או כבויה ב-volume הזה (בלי volume זו ברירת המחדל המשותפת).4 dir /x מציג עמודה של שם מקוצר ליד השם הארוך; עמודה ריקה אומרת ששם מקוצר לא נוצר. כך בודקים אצלכם את “לא בהכרח יש שם מקוצר” מסעיף 4.2.
:: מצב USN Journal
fsutil usn queryjournal C:
איפה להסתכל: מוצגים journal ID, טווח USN תקף (First USN / Next USN), MaximumSize ו-AllocationDelta.12 אחרי שיוצרים קובץ ומריצים שוב, Next USN אמור להתקדם. זו בדיקה ש”השינוי נרשם”. MaximumSize הוא מדד ל”כמה רחוק אפשר לחזור” מפרק 6. ב-volume שבו ה-journal כבוי תקבלו שגיאה.
:: בדיקת reparse point (יעד ו-tag)
dir /aL C:\Users\%USERNAME%
fsutil reparsepoint query "C:\Users\%USERNAME%\OneDrive"
איפה להסתכל: מה שמופיע ב-dir /aL הוא reparse point (עם FILE_ATTRIBUTE_REPARSE_POINT). symbolic link ו-junction מוצגים עם סוג כמו <SYMLINKD> <JUNCTION>. fsutil reparsepoint query מציג את ערך ה-reparse tag, ובסוג החלפת שם גם את נתיב היעד. יעד שאינו reparse point מחזיר שגיאה, ועצם השגיאה היא בדיקה שזו תיקייה רגילה.
אם עוקבים אחרי פעולת קובץ ב-Procmon, הדמויות מהפרק הזה זורמות בשמן האמיתי (כתיבה ל-$LogFile, נתיב עם שם stream, טיפול reparse). לשימוש ראו “מדריך מעשי ל-Process Monitor (ProcMon)”.
9. סיכום
- המרכז של NTFS הוא MFT. כל הקבצים הם רשומות בפנקס, והמידע נמצא “בתוך הרשומה” או “באזור חיצוני שהרשומה מצביעה עליו”. נתונים קטנים resident, נתונים גדולים הפניה ל-runs, והאיטיות בטיפול בהמון קבצים קטנים והפרגמנטציה הם תוצאה של המבנה הזה.1
- אפשר כמה data streams. Zone.Identifier (Mark of the Web) הוא פשוט ADS, נראה ב-
dir /r, ולא עובר מחוץ ל-NTFS.211 - שם הוא attribute, ואפשר כמה. hard link הוא שם באותו מעמד לאותה רשומה, ושם 8.3 הוא שם נוסף לתאימות. “מחיקה = להוריד שם”, והישות נעלמת כשהשם האחרון, ה-handles, והייחוסים בתוך ה-kernel (כמו mapped section) נעלמו כולם.34
- reparse point הוא hook רשמי על פתיחה, ו-symbolic link, junction ו-Files On-Demand הם יישומים שלו. קוד שעובר עץ צריך להיות מודע ל-
FILE_ATTRIBUTE_REPARSE_POINT.56 - יש שני journals.
$LogFileמשחזר עקביות מבנה (לא להישבר), USN הוא היסטוריית שינויים (מה השתנה). “יש journaling ולכן גם הנתונים בטוחים” לא נכון: עמידות הנתונים בונים בכלים מפרק 4.78 - גודל לוגי והקצאה הם שני דברים. sparse, דחיסה, ADS ועיגול ל-cluster הם ארבע הסיבות העיקריות ל”הגדלים לא מסתכמים”. כולל העניין שקובץ דחוס לא נהיה I/O אסינכרוני, זה מגירה לחקירת ביצועים.910
ההמשך בפרק האחרון, פרק 6 “filter drivers ו-minifilter: למה Procmon וסריקת אנטי-וירוס יכולים להתערב ב-I/O”. אלה שיושבים באמצע מאז פרק 1: אנטי-וירוס, Procmon, OneDrive, הצפנה. איך הם מתערבים ב-I/O. סגירת הסדרה: מי הם הדיירים שעומדים ברווח של מחסנית ההתקנים.
מאמרים קשורים
- מעמקי ה-I/O ב-Windows (פרק 1) — כל קריאה וכתיבה הופכות ל-IRP: תמונת המערכת
- מעמקי ה-I/O ב-Windows (פרק 2) — I/O סינכרוני ואסינכרוני: המשמעות האמיתית של OVERLAPPED
- מעמקי ה-I/O ב-Windows (פרק 4) — Cache Manager: מתי WriteFile באמת מגיע לדיסק
- למה מופיע Windows protected your PC
- MAX_PATH ומלכודות של נתיבים ושמות קבצים ב-Windows: הגבלת 260 תווים, שמות שמורים, נקודה בסוף, ורישיות
- מדריך מעשי ל-FileSystemWatcher: החמצות וכפילויות
- מלכודות של כונן רשת ונתיב UNC: עבודה עם שרת קבצים באפליקציה עסקית
- מדריך מעשי ל-Process Monitor (ProcMon): לאתר תוך 10 דקות הגדרה שלא נקראה או ACCESS DENIED
תחומי ייעוץ קשורים
ב-KomuraSoft LLC מטפלים בתכנון ובחקירה של אפליקציות Windows עסקיות שנשענות על מנגנוני NTFS: התנהגות מוזרה של גודל קובץ או ביצועי העתקה, ותקלות סביב links ו-streams.
מקורות
-
Microsoft Learn, Master File Table. לכל קובץ ב-volume של NTFS יש לפחות רשומה אחת ב-MFT, כולל רשומה של MFT עצמה. כל המידע על הקובץ (גודל, timestamps, הרשאות גישה, תוכן נתונים) נשמר בתוך רשומת MFT או באזור מחוץ ל-MFT שהרשומה מתארת את מיקומו. במחיקת קובץ הרשומה מסומנת כפנויה וחוזרת לשימוש, אבל גודל MFT לא מצטמצם. שמור MFT zone כדי לשמור על MFT רציף, ועם התקדמות ההקצאה מתחילה פרגמנטציה של MFT. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, File Streams. נתוני קובץ ב-NTFS נשמרים כ-stream אחד או יותר. יש default data stream (בלי שם) ו-alternate data streams עם שם. אפשר לציין stream בפורמט
filename:streamnameולפתוח ב-CreateFile. ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, Hard links and junctions. hard link הוא ייצוג במערכת הקבצים שבו כמה נתיבים באותו volume מצביעים על קובץ אחד. יוצרים ב-CreateHardLink. שינוי דרך כל קישור נראה מיד דרך הקישורים האחרים. שינוי attributes מתפשט לכל ה-hard links, אבל התצוגה ב-directory entry מתעדכנת רק בקישור שבו בוצע השינוי. וגם junction (מנגנון שמחבר directory ל-volume מקומי אחר). ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, fsutil 8dot3name. NTFS יכולה לייצר שם מקוצר בפורמט 8.3 לשם ארוך. ב-fsutil 8dot3name אפשר לבדוק ולהגדיר יצירה, להסיר שמות מקוצרים קיימים (strip), ולסרוק הפניות Registry שיושפעו מההסרה. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Reparse points. reparse point הוא אוסף של נתונים שהמשתמש מגדיר ו-reparse tag שמזהה באופן ייחודי את פורמט הנתונים. בפתיחת קובץ עם reparse point מערכת הקבצים מנסה טיפול שמתאים ל-tag (טיפול בידי file system filter שמפרש את ה-tag). משמש למימוש קישורים במערכת הקבצים של NTFS ולאחסון מרוחק (hierarchical storage). אפשר לבדוק קיום ב-attribute FILE_ATTRIBUTE_REPARSE_POINT. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Symbolic links. symbolic link הוא אובייקט במערכת הקבצים שמצביע לקובץ או directory אחר, ופועל כ-redirect שקוף ליעד. יש קישורים מוחלטים ויחסיים, ואפשר הפניה שחוצה volume או לנתיב מרוחק. ↩ ↩2 ↩3
-
Microsoft Learn, NTFS overview. NTFS משתמשת בקובץ log ובמידע checkpoint, ובמקרה של כשל מערכת מריצה את ה-transaction log באתחול הבא ומשחזרת אוטומטית את עקביות מערכת הקבצים. יש גם מיפוי דינמי מחדש של sectors פגומים, ו-self-healing NTFS שמתקן נזק קל ברקע. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Change Journals. בכל שינוי לקובץ או directory ב-volume נרשמים תוכן השינוי ושם היעד ב-USN change journal של אותו volume. ה-journal נשמר לפי volume. אפשר להשתמש בו לשחזור אינדקס מערכת קבצים אחרי כשל ולהימנע מאינדוקס מחדש של כל ה-volume. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Sparse Files. בקובץ sparse לא מקצים שטח דיסק פיזי לטווחים גדולים שמורכבים מאפסים, ומקצים אזור רק לחלקים שמכילים נתונים. קריאת טווח בלי הקצאה מחזירה אפסים. ↩ ↩2 ↩3
-
Microsoft Learn, File Compression and Decompression. דחיסת קבצים של NTFS נעשית בשקיפות, והנתונים נדחסים ומאוחסנים לפי יחידת דחיסה. ב-GetCompressedFileSize אפשר לקבל את הגודל אחרי דחיסה (הקצאה אמיתית). קריאה וכתיבה של קובץ דחוס מלוות בעלות decompress ודחיסה מחדש. ↩ ↩2 ↩3
-
Microsoft Learn, Streams - Sysinternals. כלי streams של Sysinternals יכול למנות ולמחוק alternate data streams של קובץ NTFS. ↩ ↩2
-
Microsoft Learn, Creating, Modifying, and Deleting a Change Journal ו-fsutil usn. MaximumSize של change journal הוא ערך יעד, וכשהגודל חורג מסכום MaximumSize ו-AllocationDelta הוא נגזר ב-checkpoint של NTFS. AllocationDelta הוא יחידת ההוספה לסוף ה-journal והמחיקה מההתחלה. ב-fsutil usn queryjournal אפשר לעיין במצב ובקיבולת, וב-readjournal בתוכן הרשום. מתוכנית משתמשים ב-FSCTL_CREATE_USN_JOURNAL / FSCTL_QUERY_USN_JOURNAL / FSCTL_READ_USN_JOURNAL / FSCTL_DELETE_USN_JOURNAL. מחיקה או כיבוי של journal פעיל מלווים בסריקת MFT כולה וכופים על שירותים שמשתמשים ב-journal סריקה מחדש של ה-volume. ↩ ↩2 ↩3 ↩4
מאמרים קשורים
מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.
מעמקי ה-I/O ב-Windows (פרק 4) — Cache Manager: מתי WriteFile באמת מגיע לדיסק
פרק 4 בסדרה שמסבירה בתרשימים את Cache Manager של Windows. המאמר עובר על מטמון שממומש כמיפוי קובץ, read-ahead ו-lazy writer, הבחירה בין Fl...
מעמקי ה-I/O ב-Windows (פרק 6, אחרון) — filter drivers ו-minifilter: למה Procmon וסריקת אנטי-וירוס יכולים להתערב ב-I/O
הפרק האחרון בסדרה שמסבירה בתרשימים filter drivers ו-minifilter ב-Windows. המאמר עובר על Filter Manager ו-altitude, callbacks מסוג pre/pos...
מעמקי ה-I/O של Windows (חלק 3) — I/O completion ports (IOCP) ו-thread pool של .NET: המרתף שמתחת ל-async/await
חלק 3 בסדרה עם איורים על I/O completion ports (IOCP): איחוד ה-queue עם בקרת מספר ה-threads, ערך ה-concurrency, שחרור ה-threads ב-LIFO והה...
מעמקי ה-I/O של Windows (חלק 2) — I/O סינכרוני ואסינכרוני: מה OVERLAPPED באמת אומר
חלק 2 בסדרה מסביר בדיאגרמות I/O סינכרוני ואסינכרוני (Overlapped I/O) ב-Windows: המשמעות של FILE_FLAG_OVERLAPPED, ארבע דרכי ההודעה על הסיו...
מעמקי ה-I/O של Windows (חלק 1) — כל קריאה וכתיבה הופכת ל-IRP: התמונה הכוללת של מערכת ה-I/O
חלק 1 בסדרה על ה-I/O של Windows מהיסוד: מרחב השמות של ה-Object Manager, ה-driver ואובייקטי ה-device וה-file, מחזור החיים של IRP, ומה Clos...
נושאים קשורים
העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.
נושאים טכניים ב-Windows
שער לנושאי פיתוח Windows, חקירת תקלות וניצול נכסים קיימים.
שירותים הקשורים לנושא הזה
המאמר קשור ישירות לשירותים הבאים.
פיתוח יישומי Windows
יישומים עסקיים, חיבור התקנים וכלי תקשורת, מהגדרת הדרישות ועד הפיתוח.
שאלות נפוצות
שאלות נפוצות בפניות בנושא המאמר.
- מהי MFT (Master File Table)?
- זו מבנה הנתונים בלב volume של NTFS: פנקס שמחזיק לפחות רשומה אחת (file record) לכל קובץ ב-volume, כולל רשומה ל-MFT עצמה. כל מה שקשור לקובץ, מגודל, timestamps והרשאות גישה ועד תוכן הנתונים עצמו, נשמר או בתוך רשומת MFT או באזור מחוץ ל-MFT שהרשומה מצביעה עליו. קובץ קטן נכנס כולו, כולל הנתונים, לתוך רשומת ה-MFT שלו (resident); לקובץ גדול נרשמת ברשומה רק הפניה לפריסת הנתונים (runs של clusters) (non-resident). מחיקת קובץ מסמנת את הרשומה כפנויה לשימוש חוזר, אבל גודל ה-MFT עצמו לא מצטמצם.
- מהם נתוני Zone.Identifier הבלתי נראים שמצורפים לקבצים?
- זה אחד מ-alternate data streams של NTFS. ב-NTFS קובץ אחד יכול להחזיק כמה רצפי בתים (streams); מה שקוראים וכותבים בדרך כלל הוא ה-stream ברירת המחדל בלי שם. Windows רושם מאיפה הקובץ הגיע, למשל שהורד מהאינטרנט, ב-stream נוסף שמציינים בנקודתיים, כמו file.txt:Zone.Identifier. זה מה שנקרא Mark of the Web, וזה מה ש-SmartScreen ותצוגה מוגנת של Office משתמשים בו כראיה. alternate streams לא מופיעים בתצוגת הגודל ב-Explorer; אפשר לבדוק אותם בפקודה dir /r או בכלי streams של Sysinternals. כדאי גם לשים לב שהם לא נשמרים כשמעתיקים למערכת קבצים שאינה NTFS, כמו FAT.
- מה ההבדל בין hard link ל-symbolic link?
- hard link הוא עוד שם באותו מעמד שנוסף, ומצביע על אותה ישות קובץ: אותה רשומת MFT. אפשר ליצור אותו רק בתוך אותו volume, גישה לקובץ דרך כל אחד משמותיו נותנת את אותו קובץ, ומחיקת שם אחד לא מסירה את הקובץ כל עוד שם אחר נשאר. symbolic link הוא שלט שמפנה לנתיב אחר, וממומש כ-reparse point. כי הוא מחזיק רק את נתיב היעד כמחרוזת, הוא יכול להצביע ל-volume אחר או אפילו למיקום מרוחק, אבל הופך למבוי סתום אם היעד נעלם. בפועל, כלל האצבע: hard link לשיתוף הישות הבסיסית (שמשנה את משמעות המחיקה), ו-symbolic link להפניית נתיב (להעברה או redirect).
- NTFS היא journaling file system, אז הנתונים שורדים ניתוק חשמל?
- צריך להבין בדיוק מה מוגן. מה שיומן הטרנזקציות של NTFS ($LogFile) מגן עליו הוא עקביות מבנה מערכת הקבצים (metadata). גם אם מתרחש כשל מערכת, NTFS משחזרת עקביות אוטומטית באמצעות ה-journal באתחול הבא, ומונעת מצב שבו ה-volume פגום ואינו ניתן לקריאה. אבל זה לא אומר שתוכן הקובץ שהיה באמצע כתיבה משוחזר. כמו שראינו בפרק 4 של הסדרה, נתונים dirty שיושבים במטמון אובדים בניתוק חשמל. לכן ההבנה הנכונה היא 'ה-volume לא יישבר, אבל תוכן הכתיבה האחרונה עדיין יכול להיעלם'. אם נדרשת עמידות לנתונים עצמם, צריך לבנות אותה בעצמכם, עם FlushFileBuffers, WRITE_THROUGH, או תכנון כתיבה ברמת האפליקציה כמו כתיבה לקובץ זמני ואז rename.
- למה הגודל של קובץ שונה מהגודל בדיסק?
- כי NTFS מנהלת בנפרד את האורך הלוגי של הקובץ ואת שטח הדיסק שמוקצה לו בפועל. גם בקובץ רגיל יש הבדל כלשהו כי ההקצאה מעוגלת כלפי מעלה ל-clusters שלמים (4KB כברירת מחדל), אבל קבצים sparse וקבצים דחוסים הם המקום שבו הפער נהיה גדול. קובץ sparse לא מקצה אחסון אמיתי לטווחים שנמשכים כאפסים: הוא מנהל אותם כחורים. כך שבהחלט אפשרי שקובץ בגודל לוגי של כמה GB ישתמש רק בכמה MB בדיסק. לקובץ דחוס מוקצה רק הגודל אחרי דחיסה. להפך, אם הגודל בדיסק נראה גדול יותר, עיגול ל-cluster או alternate data stream הם לעיתים קרובות הסיבה.