我在摆弄拇指驱动器时注意到了一种违反直觉的趋势。
我设置的簇大小(Windows 中的分配单元大小,Linux 中的块大小)越大,报告的容量就越小。
这很奇怪,因为基本逻辑恰恰相反 - 更大的簇应该导致更少的文件系统元数据,从而产生更多的可用空间。我在互联网上找到的每一页关于“最佳”簇大小的建议也都重复了这一点(目前已有十几页)。
这里有一些数字exFAT
。
容量(字节) | 簇大小 [KiB] | 差值 [KiB] |
---|---|---|
15792537600 | 64 | |
15792472064 | 128 | 64 |
15792340992 | 256 | 128 |
15792078848 | 512 | 256 |
15791554560 | 1024 | 512 |
15789457408 | 2048 | 2048 |
15783165952 | 4096 | 6144 |
此外,差异列中的模式在最后一行中断了......
现在NTFS
。
容量(字节) | 簇大小 [KiB] | 差值 [KiB] |
---|---|---|
15794679808 | 4 | |
15794675712 | 8 | 4 |
15794667520 | 16 | 8 |
15794667520 | 三十二 | 0 |
15794634752 | 64 | 三十二 |
15794569216 | 128 | 64 |
15794438144 | 256 | 128 |
我们再次发现一个异常差异。
方法:通过 Windows 资源管理器格式化实用程序进行格式化。通过 Windows 资源管理器属性收集容量数据。分区表:GPT。
那么为什么更大的集群会产生更少的容量呢?
随机琐事:exFAT 在 2019 年变得有点“开源”。
exFAT 文件系统规范
答案1
exFAT 的情况。
鉴于 exFAT 具有公开且易于访问的规范,它是最容易解决的示例。
使用二甲醚使用 Joep van Steen 建议的工具,我们可以检查磁盘上文件系统的确切结构。
磁盘在硬件上被划分为扇区。这是数据的基本单位。
有几种类型的元数据和效果决定了可用数据的确切数量。
分区:分区方案和分区边界本身根据一些非常简单的规则将磁盘划分为可用部分。最流行的两种方案是:MBR 和 GPT。我还没有详细研究过 MBR。
- GPT(GUID 分区表) 开销具有静态大小。它将一个跨越 34 个扇区的分区表放在磁盘的开头,将一个跨越 33 个扇区的备份表放在结尾。
- 第一个分区是没有义务紧接着分区(即 LBA34)开始。其起始(和结束)扇区由分区条目(上图中 LBA2 中的小框之一)设置。为了对齐目的,可以留出间隙^^。
文件系统结构:分区的开头是文件系统相关的结构和元数据。第一个扇区是文件系统的引导扇区(这与启动机器无关)。它定义了文件系统的类型及其布局。不同类型的文件系统根据其需求和设计将不同类型的数据放入其中。这是我的闪存驱动器的引导扇区:
exFAT 是基于 FAT 的文件系统,例如,它说明了 FAT 表有多长,以及它位于何处。
它还说明了群集堆位于何处。群集堆是用户可以在其中放置滑雪视频和猫图片的所有空间。文件系统元数据:到目前为止,我们已经测量了数据和位置部门文件系统是用来管理这些部门以一种简单而一致的方式。它根据其设计创建自己的数据单元,并根据其内部机制进行管理。(这是添加抽象层的一个例子。)这些数据单元称为簇。 每个簇可以是一个或多个相邻扇区。它们只在文件系统范围内才有意义。上面的引导扇区说明了它们有多大(2 的幂)、此集群堆从哪里开始以及我们有多少个。
- exFAT fs 中的前 2 个簇始终是空的。
- 此外,exFAT 还维护一个簇分配位图(
$BITMAP
)。位图中的每个位指定相应的簇是否空闲。 - 大写字母表(exFAT 是不区分大小写的文件系统,表格可帮助其实现这一点)。它具有固定大小。
剩余空间:我们将驱动器上的空间和分区中的空间分割成小块。但有时空间无法与所有小块整齐对齐。例如,当您为浴室铺瓷砖时,一开始铺的是整块瓷砖,但最后您无法铺整块瓷砖,因此必须将其部分切割。
但在我们的例子中,我们无法切割它,所以一点点(任何不能容纳整个的少量空间)簇)未被使用。簇尺寸越大,可能未被利用的空间就越多。
完整答案
以上所有内容构成必要和充分对 exFAT 问题的解释。
大体上,上述内容适用于所有文件系统,但细节可能有所不同(例如,另一个文件系统可能不会像 exFAT 那样将前 2 个簇留空)。
我创建了一个电子表格来演示这些关系:
观察结果:
这些数字与驱动器的报告容量完全一致。
就元数据效率而言,将哪种文件放入文件系统并不重要。(无论如何,对于 exFAT 和其他静态元数据文件系统而言
ext4
。)由于平铺问题(见上文第 4 点),文件大小仍然很重要。从统计上看,平均而言,你会浪费
(文件数)*(半簇大小)
空间根据特定驱动器/分区的大小,元数据效率会有一个最佳点(电子表格中加粗的列)。寻找最小的数字。
您可以查看每个单元格中的公式来了解每个值的计算方式。
表格中有很多附加评论
有 2 个硬编码参数(通过事后查看格式化的结果发现,而不是根据之前的数据计算得出):FAT 长度和 Cluster 堆偏移量。
- 我不知道用什么算法来计算这些。我试着查看鲁弗斯的源代码作为答案,但它只是调用本机函数(
fmifs.dll::FormatEx
)来执行实际操作。 - 这些值显然有规律
FAT len
。查看末尾的列,并注意列中出现的相同值min FAT len
。但是我没有数学能力来推断它。我欢迎帮助。
- 我不知道用什么算法来计算这些。我试着查看鲁弗斯的源代码作为答案,但它只是调用本机函数(
编辑:奖励优化时间。在某个点上,高簇大小浪费的空间大于元数据节省的空间。这取决于文件系统上的文件数量。我在最后添加了一个新列来演示这种关系。
注意:我欢迎对电子表格做出贡献。如果您希望做出贡献,请申请访问权限,我们将授予您访问权限。
^ 这里有一些抽象层,如高级格式和 NAND 页面大小,我们不会在这里讨论。这些抽象是由设备本身强加的,并且(大部分)对操作系统是透明的。
^^ 操作系统可能会在格式化时破坏抽象,以避免对齐问题。请参阅高级格式。
^^^除了1簇=1扇区,其原因我还没有详细探究过。
答案2
这不是这答案,但它可能会帮助你确定发生了什么,并扩展我的评论。我已经有一段时间没有深入研究这个问题了,所以有点生疏了。
首先,我们需要一些来自引导扇区的值:
现在我们可以计算数据区域,即减去 FAT 等元数据后剩余的文件系统区域:
数据区起始 = 保留 + (2 * 每个 FAT 的大扇区),因此数据区起始 = 7166 + (2 * 513) = 8192。
我们还可以确定每个簇的扇区,我们读取了 8 个扇区,总扇区数为 532480。因此数据区域大小 = 524288
来自 clustermap 的同一分区的集群总数:
- 乘以 sect/clus,因此 65538 * 8 = 524304
因此,我们看到差异 524304 - 524288 = 16,这实际上占 2 个集群。嗯。这实际上可能是正常的,我得检查一下。
现在我想要说的是,你可以尝试不同的集群,看看数字会发生什么变化,看看你观察到的这种奇怪现象来自哪里。
我的理论是/曾经是格式将“玩弄”保留扇区的值,以便将数据区域对齐在 4k 边界,例如,如果分区从奇数 LBA 开始,但它也可能想要避免奇数个簇或没有完全使用 FAT 扇区。
保留扇区大部分是“丢失的空间”,这可能会以某种方式影响可寻址簇数量的数学计算。但请注意,这只是一个假设。这个区域越大,簇的空间就越小。因此,通过修改它的大小,它可以对齐簇,可以避免奇数簇,并且可以确保所有 FAT 条目与实际簇相对应。
所以再说一遍,这不是答案,但也许它有助于缩小答案范围。
对于 NTFS 来说,情况就完全不同了,因为它没有像 FAT 那样的一组固定的文件系统元数据结构。$MFT 可以增大/缩小(尽管我从未见过它这样做),我认为只要有大量的空闲簇,$Bitmp 就可能非常稀疏,仅举几个区别 exFAT <> NTFS。
对于“数据区域”整体而言,这并不重要,因为文件系统元数据本身被 NTFS 视为文件。而全部的分区被分成多个簇,因此第一个扇区也是第一个簇的第一个扇区。
答案3
我编写了一个 Python 脚本来为我们提供一些见解:
def meh(i):
cluster_size_in_byte = i[0] * 1024
cluster_size_divisible_volume_size = 15794682880 - 15794682880 % cluster_size_in_byte
unknown_taken_up_size_in_byte = cluster_size_divisible_volume_size - i[1]
unknown_taken_up_size_in_cluster = unknown_taken_up_size_in_byte / cluster_size_in_byte
print((i[0], unknown_taken_up_size_in_byte, unknown_taken_up_size_in_cluster))
print("exfat:")
for i in [
(64, 15792537600),
(128, 15792472064),
(256, 15792340992),
(512, 15792078848),
(1024, 15791554560),
(2048, 15789457408),
(4096, 15783165952)
]:
meh(i)
print("ntfs:")
for i in [
(4, 15794679808),
(8, 15794675712),
(16, 15794667520),
(32, 15794667520),
(64, 15794634752),
(128, 15794569216),
(256, 15794438144)
]:
meh(i)
输出如下:
exfat:
(64, 2097152, 32.0)
(128, 2097152, 16.0)
(256, 2097152, 8.0)
(512, 2097152, 4.0)
(1024, 2097152, 2.0)
(2048, 4194304, 2.0)
(4096, 8388608, 2.0)
ntfs:
(4, 0, 0.0)
(8, 0, 0.0)
(16, 0, 0.0)
(32, 0, 0.0)
(64, 0, 0.0)
(128, 0, 0.0)
(256, 0, 0.0)
对于 NTFS 的情况,答案很简单:当簇大小变大时,分区中不可用/“不可簇化”的“剩余部分”也会变大。
对于 exFAT 的情况,这也是原因之一,但情况更为复杂,因为根据报告的容量,至少 2MiB将被占用,目的不明,而且情况变得更加复杂,因为显然,被占用的部分将至少 2 个簇大的。
但是我对 exFAT 的内部结构不太熟悉,所以我不知道该提供 2MiB / 2 簇占用部分的信息。
根据我所做的一些研究和测试(外置内存),似乎 2MiB 是一个选择“集群堆偏移”, 哪个由组成A一半大小 “FAT 偏移”(基本上是 1MiB 对齐,这与 Windows 中的分区行为一致。)
此外,显然“FAT 长度”是经常与簇大小相同,微软似乎选择确保“FAT 偏移”始终是“簇堆偏移”的一半,因此当簇大小和“FAT 长度”超过 1MiB 时,“FAT 偏移”将等于“FAT 长度”,这导致“簇堆偏移”变为 2 个簇大。(此行为未观察到/在 exfatprogs 中为默认值mkfs.exfat
。)
编辑:正如我所想到但没有写到的,“FAT 偏移量”不是“簇堆偏移量”的一半,而是“FAT 偏移量”可以全部/大部分时间为 1-MiB,即,“簇堆偏移量”中剩余的填充/间隙(如果有)位于 FAT 之后而不是之前。
不过,我还没有真正检查过dump.exfat
exfatprogs 在 Windows 中生成的内容。如果你想知道确切且经过确认的细节,你可以在 Linux 环境(甚至可能是 WSL)中亲自尝试该程序。
顺便说一句,显而易见的是,表格中报告的容量是cluster size * number of clusters
。换句话说,数据和元数据的大小在任何一个集群中与数字无关。
答案4
我将尝试回答这个乍一看确实似乎不合逻辑的问题。
首先要澄清一下文章中使用的术语。簇大小与块大小不同,因为块大小由硬件决定,但簇包含多个块,是磁盘的分配单位。
一方面,簇大小越大,磁盘上的簇越少,因此管理分配位图和 FAT 条目的簇所需的开销就越少。
另一方面,exFAT 磁盘格式(实际上是所有格式)按簇分配空间,因此如果数据(任何类型)没有占据整个簇,那么剩余空间就会被浪费。
我的想法是,不仅文件会以这种方式浪费空间,而且作为 exFAT 磁盘的一部分分配的磁盘表(或数据结构)也会浪费空间。
看看 exFAT 文件系统规范,我试图计算定义的区域(或地区)。
我统计了一下,在创建 exFAT 格式时分配的区域大约有 15 个,这些区域构成了 exFAT 格式的结构。
当定义的簇较大或较小时,这些区域中的每一个区域并不会包含更多数据,有些区域实际上更小。其中一些区域所占的空间是按簇计算的,因此当扩大簇时,浪费的空间也会扩大。
这也许可以解释一些可用空间的浪费,但发布者对浪费的测量不规则也可能指向这些表的分配错误,或文档中缺少信息。