到目前為止的 4 回中,我們看過了 I/O 要求如何流動(第 1~3 回)、快取如何承接這些要求(第 4 回)。要求最終會抵達檔案系統。這次輪到其中的代表──NTFS登場了。
視角也會跟著改變。到目前為止談的是「要求如何流動」這種動態的話題,這次要談的則是磁碟上的資料是如何擺放的這種靜態結構問題。下載檔案時附帶的那個看不見的「Zone.Identifier」,其真面目究竟是什麼;複製 1 萬個小檔案,為什麼會比複製一個相同總大小的單一檔案慢上許多;「NTFS 是日誌檔案系統所以安心」這句話究竟有幾分真──這一切,都可以從這個結構得到解釋。
本文是系列文章「Windows I/O 的深層」的第 5 回。
閱讀本回前的準備:如果已經掌握第 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
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 項目所描述位置的 MFT 外部區域中。1 刪除檔案後,項目會被標記為「空閒」並重複使用,但 MFT 本身不會縮小。此外,為了讓 MFT 保持連續,系統會保留一塊稱為 MFT 區域 的空間;隨著磁碟區逐漸被填滿,MFT 也會開始出現碎片化──這類攸關「壽命」的內容,官方文件中也有記載。1
2.2. 檔案=屬性的集合,常駐與非常駐
檔案記錄的內容是一份屬性清單:標準資訊(時間戳記等)、檔案名稱、安全性,以及資料。這裡出現了一個重要的分歧點。
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:常駐與非常駐的對比。非常駐時,記錄中所持有的只是「實際資料在哪裡、有多少」的一張表(資料游程)
資料游程的數量越多,讀取一個檔案時就需要在越分散的區域之間來回穿梭。這正是接下來要談的碎片化的真面目。
從這個結構出發,可以解釋現場中遇到的多種現象。
- 複製 1 萬個小檔案為什麼慢。每一個檔案都會觸發 MFT 記錄的建立、名稱的登記、安全性設定等中繼資料操作。相較於資料傳輸本身,台帳相關的工作反而占據了主導地位(而且這些操作中的每一項,也都會成為第 6 回將提到的篩選器的檢查對象)。
- 碎片化的真面目。非常駐資料是以「叢集連續區間(游程)的序列」形式記錄的。如果拿不到連續空間,游程的數量就會增加,讀取所需的搜尋動作也會隨之增加──這就是碎片化。游程的實際排列狀況,可以用
fsutil file layout一探究竟。 - 「資料夾」也沒有什麼特別之處。目錄是一種「持有從檔案名稱到 MFT 記錄編號之索引的檔案」。在台帳層面上,所有物件都建立在同一套機制之上。
3. 資料只是「資料流」中的一個
3.1. 一個檔案,多個位元組序列
在 NTFS 中,一個檔案可以擁有多個資料流。平常用 ReadFile/WriteFile 讀寫的,是沒有名稱的預設資料流,用 檔案名稱:資料流名稱 這種語法,就可以建立備用資料流(ADS)。2
flowchart LR
subgraph F["名為 report.docx 的檔案,對應 1 筆 MFT 記錄"]
D0["預設資料流,無名稱<br/>= 平常看到的內容"]
D1["附加資料流 Zone.Identifier<br/>來源資訊,Mark of the Web"]
D2["附加資料流,自訂名稱<br/>應用程式自訂的附加資訊"]
end
圖 4:多重資料流。檔案總管的大小顯示中只會呈現預設資料流
最貼近日常的 ADS 就是 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 停用產生功能,或移除既有的短檔名(如果有依賴短檔名記錄登錄檔路徑的舊應用程式,移除操作就會導致其損壞,因此在 strip 之前提供檢查功能,也是實務上的一個重點)。4
這裡要注意的是,短檔名是否存在,取決於環境。預設行為由登錄值 NtfsDisable8dot3NameCreation 決定,共有 4 種取值:0(在所有磁碟區上產生)、1(在所有磁碟區上都不產生)、2(依磁碟區個別設定)、3(除系統磁碟區外都不產生)。4 如果選擇 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 - 接合點/掛接點──將目錄連接到本機另一個磁碟區位置的老牌機制。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(交易日誌)是中繼資料操作的先行日誌。即使發生系統故障,NTFS 也會在下次啟動時,根據這份日誌與檢查點資訊自動復原檔案系統的一致性。7 這裡所保護的是結構。正如第 4 回所述,快取上的髒資料內容可能會在斷電時遺失──正確的理解是「磁碟區不會損毀,但最後一次寫入的內容可能會消失」。
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:稀疏檔案。「空洞」沒有配置空間,導致邏輯大小與磁碟上的大小產生落差
NTFS 壓縮會以壓縮單元為單位,將資料壓縮後儲存。10 它是透明且便利的,但代價卻不透明──每次讀寫都會觸發解壓縮與重新壓縮,也更容易加劇碎片化。而且正如第 2 回第 5 章所述,存取壓縮檔案不會以非同步方式進行(檔案系統會將其轉換為同步)。當遇到「明明已經改成非同步 I/O,卻有檔案速度提不上去」的情況時,這正是值得懷疑的地方之一。
以配置為基礎的實際大小,可以用 GetCompressedFileSize 取得。在排查「檔案大小總和」與「磁碟使用量」對不上的問題時,依序懷疑稀疏、壓縮、ADS(第 3 章)、叢集無條件進位這四個因素,是常規的做法。
8. 親自動手確認
這次也一樣,所有內容都可以在你手邊的 Windows 上直接觀察到(部分操作需要系統管理員權限)。為了讓你能夠自行判斷執行結果是否正確,每個指令都附上了該看哪裡、能看出什麼的說明。
:: 檢視備用資料流
dir /r C:\Users\%USERNAME%\Downloads
該看哪裡:在一般檔案行的下方,會出現縮排、形如 檔案名稱:Zone.Identifier:$DATA 並附帶長度的行。如果出現這一行,就表示該檔案帶有 Mark of the Web(第 3 章)。瀏覽器下載的檔案會帶有它,自己建立的檔案則不會。分別在兩個地方執行並比較,ADS 的有無就會一目了然。
:: 檢視檔案在 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 短檔名的產生設定,以及既有的短檔名
fsutil 8dot3name query C:
dir /x
該看哪裡:query 會傳回該磁碟區上短檔名產生功能是否啟用(省略磁碟區則會顯示所有磁碟區共通的預設設定)。4 dir /x 會在長檔名旁顯示短檔名欄位,如果該欄位是空的,就表示沒有產生短檔名。你可以藉此在自己的環境中驗證第 4.2 節提到的「短檔名未必存在」這個說法。
:: USN 日誌的狀態
fsutil usn queryjournal C:
該看哪裡:會顯示日誌 ID、有效 USN 的範圍(First USN / Next USN)、目標最大大小(MaximumSize)與配置單位(AllocationDelta)。12 建立一個檔案後再執行一次,Next USN 應該會往前推進,這就是「變更已被記錄」的確認方式。MaximumSize 是第 6 章提到的「能追溯多久」的參考值。在未啟用日誌的磁碟區上執行會出現錯誤。
:: 確認重新解析點(連結目標與標記)
dir /aL C:\Users\%USERNAME%
fsutil reparsepoint query "C:\Users\%USERNAME%\OneDrive"
該看哪裡:dir /aL 列出的就是重新解析點(設定了 FILE_ATTRIBUTE_REPARSE_POINT 的項目)。符號連結與接合點會以 <SYMLINKD>、<JUNCTION> 這類附帶類型的形式顯示。fsutil reparsepoint query 會顯示重新解析標記的值,如果是名稱改道類型,還會顯示連結目標的路徑。對非重新解析點的物件執行該指令會出現錯誤,出現錯誤本身正好就是「這裡是普通資料夾」的確認。
用 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
-
Microsoft Learn, Reparse points. 關於重新解析點是使用者自訂資料與唯一識別其資料格式的重新解析標記的集合、開啟帶有重新解析點的檔案時檔案系統會嘗試執行與該標記對應的處理(由理解該標記的檔案系統篩選器進行處理)、被用於 NTFS 的檔案系統連結與遠端儲存體(分層儲存)的實作,以及可以透過 FILE_ATTRIBUTE_REPARSE_POINT 屬性確認其存在等內容。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Symbolic links. 關於符號連結是指向另一個檔案或目錄的檔案系統物件、作為指向目標的透明重新導向發揮作用、存在絕對與相對兩種連結,以及可以跨磁碟區參照或參照遠端路徑等內容。 ↩ ↩2 ↩3
-
Microsoft Learn, NTFS overview. 關於 NTFS 利用日誌檔案與檢查點資訊,在發生系統故障時於下次啟動時回放交易日誌以自動復原檔案系統的一致性,以及具備對不良磁區的動態重新對應・在背景修復輕微損毀的自我修復式 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
本文是透過圖解說明 Windows 篩選驅動程式與迷你篩選驅動程式的系列最終回。整理了篩選管理員與海拔、pre/post 回呼、Procmon 與防毒軟體能夠檢查全部 I/O 的機制,直到「唯獨那個環境慢」的調查步驟。
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 的情況完全可能發生。壓縮檔案則只會配置壓縮後的大小。反過來說,如果「磁碟上的大小」顯示得比較大,原因有時出在叢集無條件進位或備用資料流。