为什么更大的集群产生的容量更少?

mar*_*ixy 13 filesystems

我在摆弄拇指驱动器时,发现了一种反直觉的趋势。

我设置的簇大小越大(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 32 0
15794634752 64 32
15794569216 128 64
15794438144 256 128

我们再次发现了一种异常差异。

方法:通过 Windows 资源管理器格式化实用程序完成格式化。通过 Windows 资源管理器属性收集的容量数据。分区表:GPT。

那么为什么更大的集群产生的容量更少呢?

随机琐事:exFAT 在 2019 年有点“开源”。
exFAT 文件系统规范

mar*_*ixy 12

exFAT 的情况。

鉴于 exFAT 具有公开且易于访问的规范,因此它是最容易解决的示例。

使用Joep van Steen 建议的DMDE工具,我们可以检查磁盘上文件系统的确切结构。

磁盘在硬件中被划分为扇区^。这是数据的基本单位。

有多种类型的元数据和效果决定了可用数据的确切数量。

  1. 分区:分区方案和分区边界本身根据一些非常简单的规则将磁盘划分为可用部分。最流行的两种方案是:MBR 和 GPT。我没有详细研究MBR。

    • GPT(GUID 分区表)开销具有静态大小。它将一个跨越 34 个扇区的分区表放在磁盘的开头,将一个跨越 33 个扇区的备份表放在磁盘的末尾。 GUID分区表
    • 第一个分区不必该分区之后立即启动(即 LBA34)。它的起始(和结束)扇区由分区条目(上图中 LBA2 中的那些小框之一)设置。为了对齐目的,可以留出间隙^^。
  2. 文件系统结构:在分区的开头,开始与文件系统相关的结构和元数据。第一个扇区是文件系统的引导扇区(这与引导机器无关)。它定义了文件系统的类型及其布局。不同类型的文件系统根据其需求和设计在其中放置不同类型的数据。这是我的闪存驱动器的引导扇区:

    exFAT 引导扇区

    exFAT 是基于 FAT 的文件系统,因此它会说明 FAT 表的长度以及它所在的位置。
    它还说明了簇堆所在的位置。簇堆是用户可以放置滑雪视频和猫图片的所有空间。

  3. 文件系统元数据:到目前为止,我们已经测量了扇区中的数据和位置。文件系统是一种用于以简单且一致的方式管理这些扇区的结构。它根据其设计创建自己的数据单元,并根据其内部机制进行管理。(这是添加抽象层的示例。)这些数据单元称为。每个可以是一个或多个连续的扇区。它们仅在文件系统范围内才有意义。上面的引导扇区说明了它们有多大(2 的幂)、簇堆开始的位置以及我们有多少个簇堆。

    • exFAT 文件系统中的前 2 个簇始终为空。
    • 此外,exFAT 还维护一个簇分配位图 ( $BITMAP)。位图中的每一位指定对应的簇是否空闲。
    • 一个大写的表(exFAT 是一个不区分大小写的文件系统,table 帮助它实现这一点)。它有固定的大小。
  4. 剩余空间:我们将驱动器上的空间以及分区中的空间切成小块。但有时空间并没有与所有的小部件整齐地对齐。就像你给浴室铺瓷砖一样,从整块瓷砖开始,但最后你不能铺整块瓷砖,所以你必须把它部分切割。 不完美的瓷砖

    除了我们的例子,我们不能削减它,所以一点点(任何不能容纳整个集群的少量空间)未被使用。大小越大,可能未使用的空间就越多。

完整答案

以上就是对exFAT问题的必要且充分的解释。

广义上,上述内容适用于所有文件系统,但细节可能有所不同(例如,另一个文件系统可能不会像 exFAT 那样将前 2 个簇留空)。

我创建了一个电子表格来演示这些关系:

exFAT 中的簇大小与可用空间

观察结果:

  • 这些数字与报告的驱动器容量完全一致^^^。

  • 就元数据效率而言,将哪种文件放入文件系统并不重要。(无论如何,对于 exFAT 以及其他静态元数据文件系统,例如ext4.)

  • 由于平铺问题,文件大小仍然很重要(参见上面的 4)。据统计,平均您将浪费
    (文件数)*(簇大小
    的一半)空间

    • 一些高级文件系统(如)btrfs能够用完这个闲置空间。
      这称为块再分配
    • 此外,某些文件系统可以“内联”存储小文件 - 以及元数据块,而不是为其分配簇。(NTFSbtrfsext4
  • 根据特定驱动器/分区的大小,元数据效率会有一个最佳点(电子表格中的粗体列)。求最小的数。

  • 您可以查看每个单元格中的公式以了解每个值的计算方式。

  • 表中还有很多附加注释

  • 有 2 个硬编码参数(通过查看格式化结果后发现,而不是根据之前的数据计算):FAT 长度和簇堆偏移量。

    • 我不知道用什么算法来计算这些。我尝试查看Rufus 的源代码来寻找答案,但它只是调用本机函数 ( fmifs.dll::FormatEx) 来执行实际操作。
    • 价值观显然有一个模式FAT len。请参阅最后的列,并注意该min FAT len列中出现的相同值。然而我没有数学能力来推论它。我欢迎帮助。
  • 编辑:额外的优化时间。在某个点上,高集群大小所浪费的空间变得大于元数据节省的空间。这取决于文件系统上的文件数量。我在最后添加了一个新列来演示这种关系。

注意:我欢迎对电子表格做出贡献。如果您希望做出贡献,请请求访问权限,您将被授予。


^ 这里有一些抽象层,例如高级格式和 NAND 页面大小,我们不会在这里讨论。这些抽象是由设备本身强加的,并且(大部分)对操作系统是透明的。
^^ 操作系统在格式化时可能会破坏抽象以避免对齐问题。请参阅高级格式
^^^ 除了1个簇=1个扇区之外,其他原因我还没有详细探讨过。

  • 多么出色且经过充分研究的答案! (2认同)

Joe*_*een 7

这不是答案,但它可以帮助您确定发生了什么并扩展我的评论。自从我深入研究这个问题以来已经有一段时间了,所以有点生锈了..

首先我们需要引导扇区的一些值:

在此输入图像描述

我们现在可以计算数据区域,即减去 FAT 等元数据后剩余的文件系统区域:

数据区起始 = 保留 + (2 * 每个 FAT 的大扇区),因此数据区起始 = 7166 + (2 * 513) = 8192。

我们还可以确定每个簇的扇区,我们读取 8 个扇区,总扇区 532480。因此数据区域大小 = 524288

来自同一分区的 clustermap 的总簇数:

在此输入图像描述

  1. 乘以宗派/宗派,即 65538 * 8 = 524304

所以我们看到差异 524304 - 524288 = 16,它实际上占了 2 个簇。嗯。其实这可能是正常的,我得检查一下。

现在我想说的是,你可以尝试不同的集群,看看数字会发生什么,看看你观察到的这种奇怪现象从何而来。

我的理论是/曾经格式将“发挥”保留扇区值,以便可能在 4k 边界对齐数据区域,例如,如果分区从奇数 LBA 开始,但它也可能希望避免奇数个簇或未完全使用FAT 扇区。

保留扇区很大程度上是“丢失的空间”,这可能会在某种程度上影响可寻址簇数量的数学计算方式。但请注意,这只是一个假设。该区域越大,集群的空间就越少。因此,通过修改它的大小,它可以对齐簇,可以避免奇数个簇,并可以确保所有 FAT 条目与实际簇相对应。

再说一遍,这不是答案,但也许有助于缩小答案范围。

对于 NTFS 来说,情况完全不同,因为它没有像 FAT 那样的一组固定的文件系统元数据结构。$MFT 可以增长/收缩(尽管我从未见过它这样做),我认为只要存在大量空闲簇,$Bitmp 就可以在很大程度上稀疏,仅举一些差异 exFAT <> NTFS。

对于整个“数据区域”来说,这并不重要,因为文件系统元数据本身被 NTFS 视为文件。并且整个分区被分为簇,因此第一个扇区也是第一个簇的第一个扇区。


Tom*_*Yan 6

我写了一个小 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)
Run Code Online (Sandbox Code Playgroud)

这是输出:

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)
Run Code Online (Sandbox Code Playgroud)

NTFS 情况的答案很简单:当簇大小变大时,分区的不可用/“不可簇”“剩余部分”也会变大。

对于 exFAT 情况,这也是原因之一,但情况更加复杂,因为根据您获得的报告容量,至少 2MiB将被用于未知目的,并且它变得更加复杂,因为显然,占用的部分至少有 2 个簇大小。

不过,我不熟悉 exFAT 的内部结构,因此我没有关于 2MiB / 2-cluster 所占用部分的信息。


根据我所做的一些研究和测试(使用exfatprogs),似乎 2MiB 是“簇堆偏移”的选择,它大小的 “FAT 偏移”组成。(基本上,1MiB 对齐,这与 Windows 中的分区行为一致。)

另外,显然“FAT 长度”通常与簇大小相同,并且微软似乎选择确保“FAT 偏移量”始终是“簇堆偏移量”的一半,因此当簇大小和“FAT Length”超过 1MiB,“FAT Offset”将等同于“FAT Length”,这会导致“Cluster Heap Offset”变成 2 个簇大小。(未观察到该行为/ exfatprogs' 中的默认行为mkfs.exfat。)

编辑:正如我想到但没有写的那样,“FAT 偏移量”不是“簇堆偏移量”的一半,“FAT 偏移量”在所有/大部分时间都可以是 1-MiB,即剩余的填充/间隙“簇堆偏移”中的 (如果有)位于 FAT 之后而不是之前。

不过,我还没有真正检查过 Windows 中使用 exfatprogs 生成的格式dump.exfat。如果您想了解确切且已确认的细节,您可以在 Linux 环境(甚至可能是 WSL)中亲自尝试该程序。


顺便说一句,显而易见的是,表中报告的容量是cluster size * number of clusters。换句话说,任何集群中的数据和元数据(的大小)与数字无关。