更新紀錄(僅初版,2026年07月29日 發布)
- 初次發布
引用本文(DOI(已登錄存檔): 10.5281/zenodo.22175375)
以下 DOI 指向先前登錄的存檔,內容可能與目前正文不同。引用目前正文時,請使用本頁網址。
Go Komura(2026)。〈Windows I/O 的深層(第 5 回) ── NTFS 的內部結構:從 MFT 理解檔案系統〉。小村軟體有限公司。 https://comcomponent.com/zh-TW/blog/ntfs-internals-mft-structure/
- DOI(已登錄存檔)
- 10.5281/zenodo.22175375
- DOI(上次登錄版本)
- 10.5281/zenodo.22175376
下載回來的檔案上附帶的那個看不見的 Zone.Identifier。總大小相同,複製 1 萬個小檔案卻特別慢的原因。「NTFS 有日誌所以放心」這個說法究竟保護了哪些範圍。本文從磁碟上資料的擺放方式出發,把這些問題整理清楚。
位於核心的,是把檔案當成台帳來管理的 MFT(主檔案表)。我們先建立「檔案就是屬性的集合」這個認識,再依資料、名稱、連結、日誌、磁碟空間的順序逐項來看。
本系列第 1~3 回談的是 I/O 要求的流動,第 4 回談的是快取的作用。這一回要談的是要求最後抵達的檔案系統,其中的代表就是 NTFS。這是把視角從「要求如何流動」這種動態的話題,轉向「資料如何擺放」這種靜態結構的一回。
本文是系列文章「Windows I/O 的深層」的第 5 回。
從遇到的問題開始讀
想依順序學習機制,可以從第 2 章開始;正在調查問題,則可以照下面的指引跳著讀。確認用的指令與輸出判讀方式,集中整理在第 8 章。
| 想知道的事、遇到的問題 | 該讀哪裡 |
|---|---|
| MFT 是什麼。為什麼刪掉檔案後 MFT 也不會縮小 | 2.1 節:磁碟區的台帳 |
| 小檔案複製很慢。想理解常駐、非常駐與碎片化 | 2.2 節:屬性與資料的存放位置 |
| 想知道 Zone.Identifier 的真面目,以及複製時會遺失的附加資訊 | 第 3 章:資料流 |
| 從別的名稱能看到相同內容。刪掉名稱後實體還在 | 4.1 節:硬連結與刪除 |
| 短檔名在某些環境並不存在。想查清楚移除短檔名的影響 | 4.2 節:8.3 短檔名 |
| 走訪資料夾時陷入迴圈。一開啟就產生網路通訊 | 第 5 章:重新解析點 |
| 斷電時究竟保住了什麼。想調查變更歷程 | 第 6 章:兩種日誌 |
| 「大小」與「磁碟上的大小」對不起來 | 第 7 章:稀疏與壓縮 |
| 想在自己手邊的 Windows 上確認 | 第 8 章:指令與輸出的判讀 |
本回會用到的前提用語
閱讀本回的前提:先掌握第 1 回的 IRP 與裝置堆疊的基礎,讀起來會更順暢。不過為了讓單獨閱讀本文也不至於卡住,先把正文中會出現、前面幾回介紹過的用語定義如下。
| 術語 | 一句話說明 | 詳見 |
|---|---|---|
| IRP(I/O Request Packet) | ReadFile 等 API 呼叫在核心內部被轉換成的「I/O 要求傳票」。驅動程式接下這張傳票後進行處理 |
第 1 回 |
| I/O 管理員與裝置堆疊 | 負責產生 IRP,並把它依序交給一路堆疊到目標裝置為止的驅動程式(堆疊)的核心元件,以及這個堆疊結構本身 | 第 1 回 |
| 快取管理員 | 把檔案內容保留在記憶體中,之後再把 WriteFile 的內容彙整寫入磁碟的元件。「寫完不代表已經送到磁碟」的元兇正是它 |
第 4 回 |
表 1:本回預設讀者已了解的前面幾回用語
還有一項:第 1 回談過的 cleanup(最後一個控制代碼關閉時)與 close(核心內部的參照全部消失時)這兩個階段,在第 4 章說明檔案刪除時也會用到。
1. 先講結論
檔案的實體與資料的存放位置
- NTFS 的核心是 MFT(主檔案表)。所有檔案都以 MFT 內的記錄形式被台帳化管理,與檔案有關的所有資訊,要麼在「MFT 項目內部」,要麼在「項目所指向的 MFT 外部區域」(第 2 章)。1
- 檔案的實體就是「屬性的集合」。小檔案會連同資料本體一起收進 MFT 記錄(常駐),大檔案則只持有指向叢集序列的參照(非常駐)。大量處理小檔案之所以慢,正是從這裡說明的(第 2 章)。1
- 資料可以有多份(多重資料流)。平常用的資料是「沒有名稱的資料流」,還可以用
file.txt:名稱掛上額外的資料流。這就是 Zone.Identifier(Mark of the Web)的真面目(第 3 章)。2
名稱,以及開啟時的處理
- 名稱也是屬性。為同一筆記錄掛上多個名稱,就是硬連結。8.3 短檔名同樣以「另一個名稱」的身分共存於同一筆記錄上(第 4 章)。34
- 重新解析點是「一開啟就轉到別處」的官方機制。符號連結、接合點、OneDrive 的隨選檔案,全都是這種帶標記資料的應用(第 5 章)。56
故障復原,以及磁碟空間的配置
- 日誌有兩種。
$LogFile用於復原中繼資料的一致性(為了不損毀而預先寫入的日誌),USN 日誌用於記錄變更歷程(記下什麼變了的台帳)。兩者的職責完全不同(第 6 章)。78 - 「大小」與「磁碟上的大小」是兩回事。稀疏檔案與壓縮會讓兩者出現落差。第 2 回談過的「壓縮檔案不會走非同步」,背景也在這裡(第 7 章)。910
圖中實線表示始終成立的關係,虛線表示附帶條件的關係(成立條件寫在詳細頁面中各關係的說明中)。關係的完整清單(共 35 條,附依據與可信度)以及主要概念的定義,彙整在知識地圖詳細頁面(日文)。資料:JSON-LD / Turtle
2. 一切都是 MFT 中的記錄
2.1. 磁碟區的台帳
格式化 NTFS 磁碟區時,會建立 MFT(master file table,主檔案表),以及一系列以 $ 開頭的中繼資料檔案。MFT 中,磁碟區上的每一個檔案都至少有一筆項目,而且MFT 自身的項目也包含在內。1
flowchart TB
subgraph VOL["NTFS 磁碟區"]
MFT["$MFT ── 主檔案表<br/>全部檔案記錄的台帳(自己也登記在裡面)"]
LOG["$LogFile ── 中繼資料操作的<br/>交易日誌(第 6 章)"]
BITMAP["$Bitmap ── 叢集的使用狀況"]
OTH["$Boot / $Secure / $UpCase 等<br/>其他中繼資料檔案"]
DATA["使用者資料區域<br/>(非常駐資料的存放位置)"]
end
MFT -->|"由記錄指向位置"| DATA
圖 1:NTFS 磁碟區的結構。「連檔案系統自身的管理資訊也以檔案形式持有」正是 NTFS 的設計
資訊不是在記錄內,就是在記錄所指向的外部區域
從大小、時間戳記、存取權限,一直到資料內容,檔案的資訊都存放在 MFT 項目內部,或是存放在由項目描述其位置的 MFT 外部區域。1
刪除騰出的記錄會被重複使用,但 MFT 本身不會縮小
刪除檔案後,該項目會被標記為「空閒」並重複使用。不過,MFT 本身的大小不會縮小。
為了讓 MFT 成長時盡可能使用連續的區域,系統會保留一塊 MFT 區域。隨著磁碟區逐漸被填滿,MFT 就會開始出現碎片化──這種運作過程中的變化,官方文件也有說明。1
2.2. 檔案=屬性的集合,常駐與非常駐
檔案記錄的內容是一份屬性清單。標準資訊(時間戳記等)、檔案名稱、安全性、資料,都由這一組屬性統一管理。
資料的存放位置分成下面兩種。
| 存放方式 | 進到 MFT 記錄裡的東西 | 資料本體的位置 |
|---|---|---|
| 常駐(resident) | 小的資料本體 | MFT 記錄內部 |
| 非常駐(non-resident) | 指向叢集序列的參照(資料游程) | MFT 外部的使用者資料區域 |
分歧的判斷請看圖 2,記錄內容的差異請看圖 3。
flowchart TB
subgraph REC["MFT 檔案記錄(單一檔案的台帳)"]
STD["標準資訊屬性<br/>時間戳記、屬性旗標"]
FN["檔案名稱屬性<br/>(可以有多個 ── 第 4 章)"]
DATA["資料屬性"]
end
Q{"資料是否夠小"}
RES["常駐(resident)<br/>資料本體直接收進記錄內<br/>讀取只需存取 MFT 即可完成"]
NONRES["非常駐(non-resident)<br/>記錄中只有「指向叢集序列的參照」<br/>實際資料放在使用者資料區域"]
DATA --> Q
Q -->|"數百位元組左右以內"| RES
Q -->|"超過這個量"| NONRES
圖 2:檔案記錄是屬性的集合。資料夠小就會「常駐」在記錄內部
把同一筆記錄的內容,在常駐與非常駐兩種情況下並排比較,會比較容易看明白。
flowchart LR
subgraph RES2["常駐(resident) ── 小檔案"]
RA["MFT 檔案記錄(固定長度)<br/>標準資訊 / 檔案名稱 / 安全性<br/>─────────────<br/>資料屬性 = 內容本身<br/>『設定值=1』就直接寫在這裡"]
RB["磁碟上沒有另外的存放位置<br/>讀取只需存取 MFT 即可完成"]
RA --> RB
end
subgraph NON2["非常駐(non-resident) ── 大檔案"]
NA["MFT 檔案記錄(固定長度)<br/>標準資訊 / 檔案名稱 / 安全性<br/>─────────────<br/>資料屬性 = 資料游程表<br/>『從哪裡開始、占幾個叢集』的排列"]
NB["使用者資料區域<br/>游程 1:一段連續的叢集"]
NC["使用者資料區域<br/>游程 2:位於別處的連續叢集"]
NA -->|"指向位置"| NB
NA -->|"指向位置"| NC
end
圖 3:常駐與非常駐的對比。非常駐時,記錄持有的只是「實際資料在哪裡、有多少」的一張表(資料游程)
資料游程的數量越多,讀一個檔案時就越需要在分散的區域之間來回穿梭。這正是接下來要談的碎片化的真面目。
從這個結構出發,可以解釋現場遇到的好幾種現象。
複製小檔案時,每個檔案的台帳處理會層層累加
每一個檔案都會產生建立 MFT 記錄、登記名稱、設定安全性這些中繼資料操作。比起資料傳輸本身,台帳工作反而占了主導地位(而且其中每一項操作,也都會成為第 6 回要談的篩選驅動程式的檢查對象)。
碎片化就是資料游程被拆散到多個區域
非常駐資料是以「叢集連續區間(游程)的序列」形式記錄的。拿不到連續區域時,游程的數量就會增加,讀取所需的搜尋(seek)次數也隨之增加──這就是碎片化。游程實際的排列狀況,可以用 fsutil file layout 一窺究竟。
目錄也是一種持有名稱索引的檔案
目錄就是「持有從檔案名稱到 MFT 記錄編號之索引的檔案」。在台帳這一層上,所有東西都建立在同一套機制之上。
3. 資料只是「資料流」中的一條
3.1. 一個檔案,多條位元組序列
在 NTFS 中,一個檔案可以擁有多條資料流。平常用 ReadFile/WriteFile 讀寫的是沒有名稱的預設資料流,而用 檔案名稱:資料流名稱 這種語法,可以建立備用資料流(ADS)。2
flowchart LR
subgraph F["名為 report.docx 的檔案(一筆 MFT 記錄)"]
D0["預設資料流(無名稱)<br/>= 平常看到的內容"]
D1[":Zone.Identifier<br/>來源資訊(Mark of the Web)"]
D2[":任意名稱<br/>應用程式自訂的附加資訊"]
end
圖 4:多重資料流。檔案總管的大小顯示中,只會算進預設資料流
Zone.Identifier 是持有來源資訊的資料流
最貼近日常的例子就是 Zone.Identifier。用瀏覽器下載的檔案,其來源(例如來自網際網路)會被記錄下來,成為 SmartScreen「Windows 已保護您的電腦」提示與 Office 受保護的檢視的判斷依據。
警告的機制我們在《Windows 為什麼會出現「Windows 已保護您的電腦」》中談過。而負責保存這份來源資訊的那一側的機制,就是 NTFS 的附加資料流。
3.2. 開發者會踩到的陷阱
一般的清單與大小顯示都看不到
它既不會出現在檔案總管顯示的大小裡,也不會出現在 dir 的清單中。可以用 dir /r 或 Sysinternals 的 streams 確認。11
依複製目的地與傳輸路徑,有可能遺失
ADS 是 NTFS 的功能,因此複製到 FAT 格式的隨身碟,或經由雲端儲存空間傳輸時往往會遺失。「明明有下載警告,複製之後卻不見了」就是這個原因。
應用程式雖然能用它,但不要拿來放業務資料的本體
只要像 CreateFile("data.txt:meta", ...) 這樣在路徑中加上冒號就能讀寫。2 雖然方便,但這樣做會把前一項的「搬不走」特性一併承接下來,因此它不是存放業務資料本體的地方。
4. 名稱同樣是屬性 ── 硬連結與 8.3 短檔名
4.1. 硬連結 ── 指向同一筆記錄的多個名稱
圖 2 中提到「檔案名稱屬性可以有多個」。在同一個磁碟區內,讓多個路徑參照單一檔案──這就是硬連結(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/>資料本體(或指向游程的參照)<br/>連結數:2"]
E1 --> REC
E2 --> REC
圖 5:硬連結。只是目錄的索引指向了同一筆 MFT 記錄,兩個名稱都是「本尊」
不論哪個名稱,指向的都是同一個檔案
不論從哪個名稱去改,都是同一個檔案,因此內容會立刻一致。3 不要把一個當成本尊、另一個當成複本,而應理解為同一個實體掛上了地位相同的多個名稱。
把「拿掉名稱」和「實體消失」分開看
有硬連結存在時,DeleteFile 的意義就變成「拿掉一個名稱」。實體真正消失的時刻,是最後一個名稱被拿掉、開啟中的控制代碼關閉,而且記憶體對應區段等核心內部的參照也全部消失之後。
第 1 回的 cleanup(最後一個控制代碼關閉時)與 close(最後一個參照消失時)這兩個階段,也關係到這裡刪除的生命週期。
內容的共用與屬性顯示的更新不是同一回事
從某個連結修改屬性後,另一個連結上看到的屬性顯示有時仍停留在舊狀態。這是官方文件也特別註記的顯示層面怪癖。3
4.2. 8.3 短檔名 ── 另一個隱藏的名稱
短檔名是為了相容性而存在的別名
出於歷史相容性的考量,NTFS 可以為長檔名自動產生 REPORT~1.DOC 這樣的 8.3 格式短檔名。它同樣是共存於同一筆記錄上的「另一個名稱」。
在檔案數量龐大的資料夾裡,產生短檔名與迴避短檔名衝突都要付出成本。可以用 fsutil 8dot3name 停用產生功能,或移除既有的短檔名。4
不過,如果有把短檔名記錄進登錄檔路徑的舊應用程式,移除短檔名就會讓它壞掉。正因如此,才會提供在 strip 之前檢查影響範圍的功能。4
先確認產生設定,再去依賴短檔名
短檔名是否存在取決於環境。決定預設行為的登錄值 NtfsDisable8dot3NameCreation 有下面 4 種取值。4
| 值 | 短檔名的產生方式 |
|---|---|
0 |
在所有磁碟區上產生 |
1 |
在所有磁碟區上都不產生 |
2 |
依磁碟區個別設定 |
3 |
除系統磁碟區以外都不產生 |
取值為 2 時可以依磁碟區切換。「只要是 Windows,就一定有 PROGRA~1 這樣的短檔名」並不成立。
在撰寫依賴短檔名的程式碼或操作步驟之前,請先用 fsutil 8dot3name query C: 確認狀態。省略磁碟區時,可以查看所有磁碟區共通的預設設定。
圍繞路徑與名稱的各種陷阱(MAX_PATH、保留名稱、結尾句點),在《MAX_PATH 與 Windows 路徑、檔案名稱的陷阱》中有詳細討論。把第 1 回的名稱解析(物件管理員)與本章(檔案系統內部的名稱)合起來看,就構成了 Windows 中「名稱」的全貌。
5. 重新解析點 ── 「一開啟就轉到別處」的機制
看標記切換開啟時的處理
檔案與目錄上都可以附加重新解析點。它的實體是一個持有標記與使用者自訂資料的屬性。
開啟帶有重新解析點的檔案時,處理會依標記而切換。一種情況是由理解該標記的篩選驅動程式接手;另一種情況是遇到名稱改道類的標記,改用連結目標的路徑重新進行解析。5
sequenceDiagram
participant App as 應用程式
participant IOM as I/O 管理員
participant FS as NTFS
App->>IOM: CreateFile("C:\data\link.txt")
IOM->>FS: IRP_MJ_CREATE(第 1 回談過的世界)
Note over FS: 在目標上發現重新解析點<br/>回傳標記與資料
alt 符號連結/接合點(名稱改道)
FS-->>IOM: 「真正的位置在這裡」
IOM->>FS: 改用連結目標的路徑重新解析
else 由篩選驅動程式管理的標記(雲端檔案等)
Note over FS: 由理解該標記的篩選驅動程式<br/>接手處理(第 6 回)
end
圖 6:重新解析點的解析過程。它是介入「開啟」這個操作的官方掛鉤
符號連結、接合點與隨選檔案
在這一套機制之上,排列著不少我們熟悉的功能。
- 符號連結(
mklink)──保存連結目標路徑的標示牌。可以指向其他磁碟區,也可以指向 UNC 路徑。6 - 接合點(junction)/掛接點──把目錄接到本機另一個磁碟區位置上的老牌機制。3
- OneDrive 的隨選檔案──用重新解析點表示實際資料不在本機的檔案,一旦被開啟,篩選驅動程式就會下載並把內容遞上來。這正是「檔案總管裡看得見,一開啟卻會發生網路通訊」的真面目(篩選驅動程式本身的機制留到第 6 回談)。
走訪目錄樹的程式碼要確認重新解析點
實務上重要的是:路徑的盡頭未必真的就是本機的那個位置。沒有考慮到它存在的程式碼,會踩到下面這些問題。
- 遞迴走訪在接合點上陷入迴圈。
- 大小統計出現重複計算。
- 備份大量觸發雲端檔案的實體化。
對策的第一步,是在 FindFirstFile 系列函式中確認 FILE_ATTRIBUTE_REPARSE_POINT。5
6. 兩種日誌 ── $LogFile 與 USN
常有人說「NTFS 是日誌檔案系統」,但 NTFS 裡有職責不同的兩種日誌。一旦混為一談,就會誤讀它到底保證了什麼。
flowchart TB
subgraph J1["$LogFile ── 先行日誌(為了不損毀)"]
A1["把中繼資料操作(記錄更新、改名等)<br/>在執行前先寫入日誌"]
A2["系統故障後的下次啟動時<br/>回放日誌以復原結構的一致性"]
A1 --> A2
end
subgraph J2["USN 日誌 ── 變更歷程(為了知道發生了什麼變化)"]
B1["檔案/目錄每次發生變更時<br/>記錄變更內容與名稱"]
B2["備份、搜尋索引、同步工具藉此<br/>不必全面掃描就能掌握「自上次以來變了什麼」"]
B1 --> B2
end
圖 7:兩種日誌。$LogFile 是為了「不弄壞」,USN 是為了「知道發生了變更」
把差異整理成表格如下。
| 觀點 | $LogFile(交易日誌) |
USN 日誌(變更日誌) |
|---|---|---|
| 目的 | 在故障後把檔案系統的結構恢復到一致狀態7 | 事後得知「自上次以來變了什麼」8 |
| 記錄的內容 | 中繼資料操作(記錄更新、改名等)的先行日誌。檔案內容不在範圍內 | 每次變更時,記錄變更的內容與目標檔案/目錄的名稱8 |
| 由誰使用 | NTFS 自身。用於下次掛接時的自動復原 | 備份、搜尋索引、同步工具等應用程式 |
| 能追溯多久以前 | 只涵蓋復原所需的範圍。由於是循環使用固定大小的空間,無法用來追溯過去的歷程 | 超過目標最大大小(MaximumSize)後,會在檢查點時刻從最舊的記錄開始截斷。能追溯的範圍取決於容量設定與磁碟區的變更量12 |
| 查看方式 | 沒有讀取其內容的官方手段(大小可以用 chkdsk /L 確認) |
用 fsutil usn queryjournal 看狀態,用 fsutil usn readjournal 看內容。從程式中則使用 FSCTL_QUERY_USN_JOURNAL / FSCTL_READ_USN_JOURNAL12 |
| 能否停用 | 不能停用(它是 NTFS 的一部分) | 管理員可以刪除或停用。但這會迫使正在使用它的服務進行全面掃描,影響很大12 |
表 2:兩種日誌的對比
$LogFile 復原結構的一致性
$LogFile 是中繼資料操作的先行日誌。系統發生故障後,NTFS 會在下次啟動時根據日誌與檢查點資訊自動復原檔案系統的一致性。7
這裡守住的是結構的一致性,而不是寫進檔案的資料內容本身。正如第 4 回所述,快取上的髒資料可能會因斷電而遺失。請把「能復原結構」和「最後寫入的內容還在」分開來考慮。
USN 是查清楚自上次以來變了什麼的台帳
磁碟區內的檔案或目錄每發生一次變更,USN 日誌就會記錄變更的內容與目標的名稱。8
它的用途,是讓備份與索引程式不必全面掃描就能挑出「自上次以來變過的部分」,也用於避免故障後重建整個索引。8
調查時,可以把 fsutil usn readjournal 當成彌補 FileSystemWatcher 漏通知的比對台帳來用。監視本身的漏通知問題,請參閱《FileSystemWatcher 實務指南》。
7. 稀疏與壓縮 ── 「大小」有兩個的故事
在 NTFS 中,檔案的邏輯長度與實際配置的區域是分開管理的,也就是內容對話方塊裡的「大小」與「磁碟上的大小」。造成兩者落差的代表性原因有兩個。
稀疏把連續為零的範圍當成「空洞」持有
稀疏檔案不會為連續為零的範圍配置實際空間,而是當成「空洞」來管理。9 邏輯大小 42GB 的虛擬磁碟檔案,在磁碟上只用了 500MB──這種情況完全是家常便飯。讀取空洞會傳回零,寫入時則會依寫入的量配置空間。
flowchart LR
subgraph L["邏輯上的檔案(大小:1GB)"]
R1["資料 10MB"]
H1["空洞(全零) 500MB"]
R2["資料 5MB"]
H2["空洞(全零) 剩餘部分"]
end
subgraph P["磁碟上的配置(15MB 加管理資訊)"]
A1["游程:R1 的實體"]
A2["游程:R2 的實體"]
end
R1 --> A1
R2 --> A2
圖 8:稀疏檔案。「空洞」沒有配置空間,邏輯大小與磁碟上的大小因此產生落差
壓縮不只影響使用量,也牽動 I/O 的行為
NTFS 壓縮會以壓縮單元為單位,把資料壓縮後存放。10 它是透明的,很方便,但代價並不透明──每次讀寫都要解壓縮與重新壓縮,也更容易加劇碎片化。而且正如第 2 回第 5 章所述,存取壓縮檔案不會變成非同步(檔案系統會把它轉換成同步)。當遇到「改成非同步 I/O 之後,卻有些檔案沒有變快」時,這裡是值得懷疑的地方之一。
大小對不起來時要看的四個因素
以配置量計算的實際大小,可以用 GetCompressedFileSize 取得。當「檔案大小的合計」與「磁碟使用量」對不起來時,請依序確認下面 4 項。
| 因素 | 要確認的內容 |
|---|---|
| 稀疏 | 連續為零的範圍,是不是成了沒有實際空間的「空洞」 |
| 壓縮 | 資料是不是以壓縮後的形式存放 |
| ADS | 有沒有預設資料流以外的資料(第 3 章) |
| 叢集無條件進位 | 差異是不是來自以叢集為單位的配置 |
把邏輯長度與已配置的區域分開來看,就比較容易追查顯示上的差異。
8. 親自動手確認
這一回同樣可以在自己手邊的 Windows 上全部觀察到(部分操作需要系統管理員權限)。為了讓你能自行判斷執行結果是否正確,每個指令都附上該看哪裡、能看出什麼。
8.1. 透過帶資料流名稱的行判斷有沒有 ADS
:: 檢視備用資料流
dir /r C:\Users\%USERNAME%\Downloads
該看哪裡:在一般檔案行的下方,會縮排排列出形如 檔案名稱:Zone.Identifier:$DATA 並附帶長度的行。只要有這一行,就表示該檔案帶著 Mark of the Web(第 3 章)。用瀏覽器下載的檔案會帶,自己建立的檔案不會帶。在這兩類位置分別執行並比較,ADS 的有無就一目了然。
8.2. 檢視常駐、非常駐與資料的配置
:: 檢視檔案在 MFT 上的配置(游程)與屬性
fsutil file layout C:\path\to\file.dat
:: 只檢視範圍區段(官方文件中有記載的子指令)
fsutil file queryextents C:\path\to\file.dat
該看哪裡:layout 會依資料流逐一列出大小、配置大小,以及非常駐時的範圍區段(VCN、LCN、叢集數這一組)清單。完全不出現範圍區段行的極小檔案就是常駐(2.2 節),如果分成多行就表示存在碎片化。分別對幾個位元組的文字檔與幾百 MB 的檔案執行並比較,是體會常駐與非常駐差異最快的方法。
8.3. 比對短檔名的產生設定與實際的名稱
:: 8.3 短檔名的產生設定,以及既有的短檔名
fsutil 8dot3name query C:
dir /x
該看哪裡:query 會傳回該磁碟區上短檔名產生功能是啟用還是停用(省略磁碟區時會傳回所有磁碟區共通的預設設定)。4 dir /x 會在長檔名旁邊顯示短檔名欄位,所以該欄位是空的就表示沒有產生短檔名。這樣就能在自己的環境中驗證 4.2 節所說的「短檔名未必存在」。
8.4. 檢視 USN 日誌的狀態與記錄推進的情形
:: USN 日誌的狀態
fsutil usn queryjournal C:
該看哪裡:會顯示日誌 ID、有效 USN 的範圍(First USN / Next USN)、目標最大大小(MaximumSize)與配置單位(AllocationDelta)。12 隨便建立一個檔案後再執行一次,Next USN 應該已經往前推進,這就確認了「變更確實被記錄下來」。MaximumSize 是第 6 章提到的「能追溯多久以前」的參考值。在日誌被停用的磁碟區上執行會出現錯誤。
8.5. 檢視重新解析點的屬性與標記
:: 確認重新解析點(連結目標與標記)
dir /aL C:\Users\%USERNAME%
fsutil reparsepoint query "C:\Users\%USERNAME%\OneDrive"
該看哪裡:dir /aL 列出來的就是重新解析點(設定了 FILE_ATTRIBUTE_REPARSE_POINT 的項目)。符號連結與接合點會帶上 <SYMLINKD>、<JUNCTION> 這類類別標示顯示出來。fsutil reparsepoint query 會顯示重新解析標記的值,如果屬於名稱改道類,還會顯示連結目標的路徑。對不是重新解析點的對象執行會出現錯誤,所以出現錯誤本身就是「這裡是普通資料夾」的確認。
8.6. 實際的檔案操作用 Procmon 追蹤
用 Procmon 追蹤檔案操作時,這一回登場的角色都會以真實名稱流過畫面(對 $LogFile 的寫入、帶有資料流名稱的路徑、重新解析處理)。使用方式請參閱《Process Monitor(ProcMon)實戰指南》。
9. 總結
- NTFS 的核心是 MFT。所有檔案都是台帳中的記錄,資訊要麼在「記錄內部」,要麼在「記錄所指向的外部區域」。小資料是常駐,大資料是游程參照,大量處理小檔案之所以慢以及碎片化,都是這個結構的必然結果。1
- 資料流可以有多條。Zone.Identifier(Mark of the Web)不過是一條普通的 ADS,用
dir /r就能看見,而且帶不出 NTFS 之外。211 - 名稱是屬性,而且可以有多個。硬連結是指向同一筆記錄的同等地位名稱,8.3 短檔名則是為相容而存在的另一個名稱。「刪除=拿掉一個名稱」,實體消失的時刻是最後一個名稱、控制代碼,以及核心內部的參照(已對應的區段等)全部消失之後。34
- 重新解析點是介入「開啟」的官方掛鉤,符號連結、接合點、隨選檔案都是它的應用。走訪目錄樹的程式碼必須留意
FILE_ATTRIBUTE_REPARSE_POINT。56 - 日誌有兩種。
$LogFile負責復原結構的一致性(不弄壞),USN 負責變更歷程(什麼變了)。「因為有日誌所以資料也安全」並不成立──資料的持久性要用第 4 回介紹的工具去打造。78 - 邏輯大小與已配置空間是兩回事。稀疏、壓縮、ADS、叢集無條件進位是「大小對不起來」的四大因素。再加上壓縮檔案不會走非同步 I/O 這一點,都能成為效能調查時的備選思路。910
接下來是最終回,第 6 回《篩選驅動程式與迷你篩選驅動程式 ── Procmon 與病毒掃描為何能夠介入 I/O》。從第 1 回開始就不時登場的那些「夾在中間的角色」──防毒軟體、Procmon、OneDrive、加密──究竟是如何介入 I/O 的。作為本系列的完結篇,我們將揭開站在裝置堆疊縫隙中的這些住客的真面目。
相關文章
- Windows I/O 的深層(第 1 回) ── 所有讀寫都會變成 IRP:I/O 系統全貌
- Windows I/O 的深層(第 2 回) ── 同步 I/O 與非同步 I/O:OVERLAPPED 的真正含義
- Windows I/O 的深層(第 4 回) ── 快取管理員:你的 WriteFile 究竟何時送達磁碟
- Windows 為什麼會出現「Windows 已保護您的電腦」
- MAX_PATH 與 Windows 路徑、檔案名稱的陷阱 ── 260 字元限制、保留名稱、結尾句點、大小寫
- FileSystemWatcher 實務指南 - 漏通知與重複通知的對策
- 網路磁碟機與 UNC 路徑的陷阱 ── 業務應用程式處理檔案伺服器(共用資料夾)的實務
- Process Monitor(ProcMon)實戰指南 ── 在 10 分鐘內找出「設定未被讀取」「ACCESS DENIED」的原因
相關諮詢領域
合同會社小村軟體承接根植於 NTFS 機制的 Windows 業務應用程式設計與調查,包括檔案大小與複製效能難以理解的行為、與連結、資料流相關的缺陷等。
參考連結
-
Microsoft Learn, Master File Table. 關於 NTFS 磁碟區上的每一個檔案在 MFT 中都至少有一筆項目、MFT 自身的項目也包含在內,檔案的大小、時間戳記、存取權限、資料內容等所有資訊都存放在 MFT 項目內部或由 MFT 項目描述其位置的 MFT 外部區域,刪除檔案時項目會被標記為空閒並重複使用但 MFT 的大小不會縮小,為保持 MFT 連續而保留 MFT 區域,以及隨著配置的進行 MFT 會出現碎片化等內容。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, File Streams. 關於 NTFS 的檔案資料以一條或多條資料流的形式儲存、存在預設的(無名稱)資料流與具名的備用資料流,以及可以用「檔案名稱:資料流名稱」的形式指定資料流並透過 CreateFile 開啟等內容。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Hard links and junctions. 關於硬連結是同一磁碟區內多個路徑參照單一檔案的檔案系統層級表示、以 CreateHardLink 建立、透過任一連結所做的修改都能從其他連結立即看到、屬性的修改會傳播到所有硬連結但目錄項目上的顯示只會在進行修改的那個連結上更新這一顯示層面的怪癖,以及接合點(把目錄接到本機另一個磁碟區的機制)等內容。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, fsutil 8dot3name. 關於 NTFS 能夠為長檔名產生 8.3 格式的短檔名、可以用 fsutil 8dot3name 查詢與設定短檔名產生功能的啟用與停用、移除(strip)既有的短檔名,以及移除時能夠掃描受影響的登錄檔參照等內容。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Reparse points. 關於重新解析點是使用者自訂資料與唯一識別該資料格式的重新解析標記的集合、開啟帶有重新解析點的檔案時檔案系統會嘗試執行與該標記對應的處理(由解釋該標記的檔案系統篩選驅動程式進行處理)、它被用於實作 NTFS 的檔案系統連結與遠端儲存體(分層儲存),以及可以透過 FILE_ATTRIBUTE_REPARSE_POINT 屬性確認其存在等內容。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Symbolic links. 關於符號連結是指向另一個檔案或目錄的檔案系統物件、它作為指向連結目標的透明重新導向而發揮作用、存在絕對連結與相對連結,以及可以跨磁碟區參照或參照遠端路徑等內容。 ↩ ↩2 ↩3
-
Microsoft Learn, NTFS overview. 關於 NTFS 使用日誌檔案與檢查點資訊、在發生系統故障時於下次啟動時回放交易日誌以自動復原檔案系統的一致性,以及具備不良磁區的動態重新對應與在背景修復輕微損毀的 self-healing NTFS 等內容。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Change Journals. 關於磁碟區內的檔案或目錄每次被修改時,該磁碟區的 USN 變更日誌都會記錄變更內容與目標檔案/目錄的名稱、日誌依磁碟區個別維護,以及可用於故障後復原檔案系統索引從而避免對整個磁碟區重新建立索引等內容。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Sparse Files. 關於稀疏檔案不會為由零構成的大範圍區域配置實體磁碟空間、只為包含資料的部分配置空間,以及讀取未配置的範圍會傳回零等內容。 ↩ ↩2 ↩3
-
Microsoft Learn, File Compression and Decompression. 關於 NTFS 的檔案壓縮是透明進行的、資料按壓縮單元逐一壓縮並存放、可以用 GetCompressedFileSize 取得壓縮(實際配置)後的大小,以及讀寫壓縮檔案會伴隨解壓縮與重新壓縮的開銷等內容。 ↩ ↩2 ↩3
-
Microsoft Learn, Streams - Sysinternals. 關於 Sysinternals 的 streams 工具能夠列舉、刪除 NTFS 檔案的備用資料流等內容。 ↩ ↩2
-
Microsoft Learn, Creating, Modifying, and Deleting a Change Journal與fsutil usn. 關於變更日誌的 MaximumSize 是一個目標值、大小超過 MaximumSize 與 AllocationDelta 之和時會在 NTFS 的檢查點時刻被截斷、AllocationDelta 是向日誌末尾附加與從開頭刪除的單位、可以用 fsutil usn queryjournal 查看日誌的狀態與容量並用 readjournal 查看記錄內容、從程式中使用 FSCTL_CREATE_USN_JOURNAL / FSCTL_QUERY_USN_JOURNAL / FSCTL_READ_USN_JOURNAL / FSCTL_DELETE_USN_JOURNAL,以及刪除或停用使用中的日誌會伴隨對整個 MFT 的掃描,並迫使正在使用該日誌的服務對磁碟區重新掃描等內容。 ↩ ↩2 ↩3 ↩4
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
Windows I/O 的深層(第 4 回) ── 快取管理員:你的 WriteFile 究竟何時送達磁碟
本文是以圖解說明 Windows 快取管理員的系列第 4 回。整理了以檔案對應方式實作的快取、預先讀取與延遲寫入、FlushFileBuffers 與 FILE_FLAG_NO_BUFFERING 的分別使用時機,以及斷電導致資料消失的條件。
Windows I/O 的深層(第 2 回) ── 同步 I/O 與非同步 I/O:OVERLAPPED 的真正含義
本文為以圖解說明 Windows 同步 I/O 與非同步 I/O(重疊 I/O)的系列文章第 2 回,將完整整理 FILE_FLAG_OVERLAPPED 的意義、完成通知的 4 種方式、明明是非同步卻同步完成的條件、取消的作法,直到與 .NET 的對應關係。
Windows I/O 的深層(第 1 回) ── 所有讀寫都會變成 IRP:I/O 系統全貌
本文是從根本解說 Windows I/O 系統的系列文章第 1 回,透過圖解整理物件管理員的命名空間、驅動程式・裝置・檔案這三種物件、IRP 的生命週期,直到 CloseHandle 幕後的機制。
Windows I/O 的深層(第 6 回・最終回) ── 迷你篩選驅動程式的機制與用 Procmon 調查延遲
說明迷你篩選驅動程式監控與控制檔案 I/O 的機制。整理 FltMgr、高度、pre/post 回呼與 fltmc 的讀法,並彙整用 Procmon 找出慢操作的步驟,以及排除設定與 Dev Drive 的注意事項。
Windows I/O 的深層(第 3 回) ── I/O 完成埠(IOCP)與 .NET 執行緒集區:async/await 的地下室
本文是以圖解說明 I/O 完成埠(IOCP)的系列文章第 3 回。將完成佇列與執行緒數控制合而為一的設計、並行值與 LIFO 釋放、.NET 執行緒集區,直到 async/await 接續實際執行的執行緒為止,逐一整理。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
常見問題
整理諮詢這個主題時常見的問題。
- MFT(主檔案表)是什麼?
- 這是位於 NTFS 磁碟區核心位置的資料結構,也是一本台帳:磁碟區上的每一個檔案至少都會有一筆項目(檔案記錄),MFT 自身的項目也包含在內。檔案的大小、時間戳記、存取權限,乃至資料本體,所有與檔案相關的資訊,要麼存放在 MFT 項目內部,要麼存放在 MFT 項目所指向的 MFT 外部區域。小檔案會連同資料本體一起收進 MFT 項目內(常駐),大檔案則只會在項目中記錄指向資料存放位置(叢集的排列)的參照(非常駐)。刪除檔案後,項目會被標記為空閒並重複使用,但 MFT 本身的大小不會縮小。
- 檔案上附帶的那個看不見的「Zone.Identifier」資料是什麼?
- 這是 NTFS 多重資料流(備用資料流)的其中一條。在 NTFS 中,一個檔案可以擁有多條位元組序列(資料流),平常讀寫的是沒有名稱的預設資料流。Windows 會把檔案的來源(例如是否從網際網路下載)記錄在像「file.txt:Zone.Identifier」這樣以冒號區隔指定的附加資料流裡。這就是所謂的「Mark of the Web」,也是 SmartScreen 警告與 Office 受保護的檢視的判斷依據。備用資料流不會出現在檔案總管顯示的大小中,可以用 dir /r 指令或 Sysinternals 的 streams 工具確認。另外要注意,複製到 NTFS 以外的檔案系統(例如 FAT)時,它不會被保留。
- 硬連結和符號連結有什麼不同?
- 硬連結是「讓另一個地位相同的名稱,指向同一個檔案實體(同一筆 MFT 記錄)」。它只能在同一個磁碟區內建立,不論從哪個名稱存取都是同一個檔案;即使刪掉其中一個名稱,只要還有其他名稱存在,檔案就不會消失。符號連結則是「指引到另一條路徑的標示牌」,以重新解析點的形式實作。它只保存著目標路徑的字串,因此可以指向其他磁碟區甚至遠端位置,但目標一旦消失,連結就走到了死路。實務上的基本分工是:硬連結用於共用實體(它改變了「刪除」的意義),符號連結用於路徑改道(搬遷或重新導向)。
- NTFS 是日誌檔案系統,所以即使斷電資料也不會遺失嗎?
- 需要正確理解它所保護的範圍。NTFS 的交易日誌($LogFile)所保護的,是檔案系統結構(中繼資料)的一致性。即使發生系統故障,NTFS 也會在下次啟動時利用日誌自動復原一致性,避免「磁碟區損毀而讀不出來」的局面。但這並不代表寫到一半的檔案資料內容本身會被復原。正如本系列第 4 回所述,快取上的髒資料會因斷電而遺失。也就是說,正確的理解是「磁碟區不會壞,但最後寫入的內容可能會消失」;如果需要資料本身的持久性,就必須靠 FlushFileBuffers、WRITE_THROUGH,或是在應用程式端的寫入設計(暫存檔案加上重新命名等)中自行落實。
- 為什麼檔案的「大小」和「磁碟上的大小」會不一樣?
- 因為在 NTFS 中,檔案的邏輯長度與實際配置的磁碟空間是分開管理的。即使是一般檔案,由於會以叢集為單位(預設 4KB)無條件進位配置,也會產生差異;但真正造成大幅落差的是稀疏檔案與壓縮檔案。稀疏檔案不會為連續為零的範圍配置實際空間,而是當成「空洞」來管理,因此邏輯大小有幾 GB、磁碟上卻只占用幾 MB 的情況完全可能發生。壓縮檔案則只會配置壓縮後的大小。反過來說,如果「磁碟上的大小」看起來比較大,原因有時出在叢集無條件進位或備用資料流。