在前面的 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 格式的 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/>数据本体,或指向运行的引用<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 路径・文件名的陷阱 ── 260 字符限制、保留名称、末尾句点、大小写》中详细讨论过。把第 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)实战指南 —— 10 分钟定位「配置未生效」「ACCESS DENIED」问题》。
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 I/O 系列的第 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
本文是通过图解讲解 Windows 过滤器驱动程序与迷你过滤器的系列最终回。整理过滤器管理器与高度、pre/post 回调、Procmon 与杀毒软件能够检查全部 I/O 的机制,直至「唯独那个环境很慢」的排查步骤。
Windows I/O 的深层(第2回) ── 同步 I/O 与异步 I/O:OVERLAPPED 的真正含义
本文是通过图解讲解 Windows 同步 I/O 与异步 I/O(重叠 I/O)的系列第 2 回。整理 FILE_FLAG_OVERLAPPED 的含义、完成通知的四种方式、明明是异步却同步完成的条件、取消的正确做法,以及与 .NET 的对应关系。
卷影复制服务(VSS)的原理与实务 ── 使用中文件为何能够备份
使用中的文件明明会因共享冲突而无法复制,备份软件为什么却能正常备份?本文将解说卷影复制服务(VSS)中请求者・编写器・提供程序的角色分工、写时复制的原理、vssadmin 的实务操作,以及差异区域的陷阱。
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
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 的情况完全可能出现。压缩文件则只分配压缩后的大小。反过来,如果“磁盘上的大小”显示得更大,原因有时在于簇取整或备用数据流。