更新记录(仅首版,2026年07月29日 发布)
- 首次发布
引用本文(DOI(已登记存档): 10.5281/zenodo.22175373)
以下 DOI 指向先前登记的存档,内容可能与当前正文不同。引用当前正文时,请使用本页网址。
Go Komura(2026)。《Windows I/O 底层原理(第 5 篇)——NTFS 的内部结构:从 MFT 理解文件系统》。小村软件有限公司。 https://comcomponent.com/zh-CN/blog/ntfs-internals-mft-structure/
- DOI(已登记存档)
- 10.5281/zenodo.22175373
- DOI(上次登记版本)
- 10.5281/zenodo.22175374
下载来的文件上附带的那个看不见的 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) | 指向簇序列的引用(Data Run) | 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/>数据属性 = Data Run 表<br/>“从哪里开始、占几个簇”的列表"]
NB["用户数据区域<br/>Data Run 1 是一段连续的簇"]
NC["用户数据区域<br/>Data Run 2 是位于别处的连续簇"]
NA -->|"指向其位置"| NB
NA -->|"指向其位置"| NC
end
图 3:常驻与非常驻的对比。非常驻情况下,记录持有的只是“实际数据在哪里、有多少”的一张表(Data Run)
Data Run 的数量越多,读一个文件时就越需要在分散的区域之间来回跳转。这就是接下来要讲的碎片化的真相。
从这一结构出发,可以解释现场遇到的好几种现象。
复制小文件时,按文件计的台账处理会层层累加
每一个文件都会产生创建 MFT 记录、登记名称、配置安全信息这些元数据操作。比起数据传输本身,台账工作反而占了主导(而且其中每一次操作,都会成为第 6 篇要讲的过滤器的检查对象)。
碎片化就是 Data Run 被拆散到多个区域
非常驻数据是以“连续簇区段(Data Run)的序列”形式记录的。拿不到连续区域时,Data Run 的数量就会增加,读取所需的寻道次数也随之增加——这就是碎片化。Data Run 的实际排列可以用 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 格式的 U 盘或云存储复制时往往会丢失。“复制之后下载警告就没了”就是这个原因。
应用虽然能用它,但不要把业务数据本体放进去
只要像 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/>数据本体(或指向 Data Run 的引用)<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 - 联接点 / 装载点——把目录连到本地另一个卷的位置上,是资历很老的机制。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["Data Run R1 的实体"]
A2["Data Run 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 上的布局(Data Run)与属性
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。全部文件都是台账中的记录,信息要么在“记录内”,要么在“记录所指向的外部区域”。小数据常驻,大数据用Data Run 引用,大量处理小文件之所以慢以及碎片化,都是这一结构的必然结果。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 底层原理(第 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 底层原理(第 2 篇)——同步 I/O 与异步 I/O:OVERLAPPED 的真正含义
本文是图解讲解 Windows 同步 I/O 与异步 I/O(重叠 I/O)的系列第 2 篇。梳理 FILE_FLAG_OVERLAPPED 的含义、完成通知的 4 种方式、本应异步却同步完成的条件、取消的正确做法,以及与 .NET 的对应关系。
OneDrive“按需文件”与业务应用——占位符打破的前提与对策
桌面上的 CSV 读不了,导入处理以“找不到文件”中断——原因可能是 OneDrive 的 KFM 与按需文件。本文讲解占位符的机制与属性判断,以及应用端和信息系统部门各自的对策。
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
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)保护的是文件系统结构(元数据)的一致性。即使发生系统故障,下次启动时也会用日志自动恢复一致性,避免出现“卷损坏读不出来”的局面。但这并不意味着写到一半的文件数据内容本身会被还原。正如本系列第 4 篇所讲,缓存中的脏数据会因断电而丢失。也就是说,正确的理解是“卷不会坏,但最后写入的内容可能消失”;如果需要数据本身的持久性,就要用 FlushFileBuffers、WRITE_THROUGH,或者在应用侧的写入设计(临时文件加重命名等)中自行落实。
- 文件的“大小”和“占用空间”为什么不一样?
- 因为在 NTFS 中,文件的逻辑长度和实际分配的磁盘空间是分开管理的。即使是普通文件,由于按簇为单位(默认 4KB)向上取整分配,也会产生差异;但真正造成大幅偏离的是稀疏文件和压缩文件。稀疏文件不会为连续为零的范围分配实际空间,而是当作“空洞”来管理,因此逻辑大小几 GB、磁盘上只占几 MB 的情况完全可能出现。压缩文件则只分配压缩后的大小。反过来,如果“占用空间”看起来更大,原因有时是簇向上取整或备用数据流。