Squashfs 4.0 文件系统

Squashfs 是一个用于 Linux 的压缩只读文件系统。

它使用 zlib、lz4、lzo、xz 或 zstd 压缩来压缩文件、inode 和目录。系统中的 inode 非常小,并且所有块都被打包以最大限度地减少数据开销。支持大于 4K 的块大小,最大可达 1M 字节(默认块大小为 128K)。

Squashfs 适用于通用的只读文件系统用途,适用于归档用途(即在可以使用 .tar.gz 文件的情况下),以及在受限的块设备/内存系统(例如,嵌入式系统)中,需要低开销。

邮件列表(内核代码):linux-fsdevel@vger.kernel.org 网站:github.com/plougher/squashfs-tools

1. 文件系统特性

Squashfs 文件系统特性与 Cramfs 比较

最大文件系统大小

2^64

256 MiB

最大文件大小

~ 2 TiB

16 MiB

最大文件数

无限制

无限制

最大目录数

无限制

无限制

每个目录的最大条目数

无限制

无限制

最大块大小

1 MiB

4 KiB

元数据压缩

目录索引

稀疏文件支持

尾端打包(片段)

可导出(NFS 等)

硬链接支持

readdir 中的“.”和“..”

真实的 inode 编号

32 位 uids/gids

文件创建时间

Xattr 支持

ACL 支持

Squashfs 压缩数据、inode 和目录。此外,inode 和目录数据高度压缩,并在字节边界上打包。每个压缩 inode 的平均长度为 8 字节(确切长度随文件类型而变化,即常规文件、目录、符号链接和块/字符设备 inode 具有不同的大小)。

2. 使用 Squashfs

由于 squashfs 是只读文件系统,因此必须使用 mksquashfs 程序来创建填充的 squashfs 文件系统。这个和其他 squashfs 实用程序很可能由您的 linux 发行版打包(称为 squashfs-tools)。源代码可以从 github.com/plougher/squashfs-tools 获取。使用说明也可以从该站点获取。

2.1 挂载选项

errors=%s

指定 squashfs 错误是否触发内核 panic

continue

错误不会触发 panic(默认)

panic

遇到错误时触发 panic,类似于其他几个文件系统(例如 btrfs、ext4、f2fs、GFS2、jfs、ntfs、ubifs)

这允许保存内核转储,有助于分析和调试损坏。

threads=%s

选择解压缩模式或线程数

如果设置了 SQUASHFS_CHOICE_DECOMP_BY_MOUNT

single

使用单线程解压缩(默认)

任何时候只能解压缩一个块(数据或元数据)。这会将 CPU 和内存使用量限制在最低水平,但由于等待解压缩程序的可用性,在使用多 CPU 机器时,在并行 I/O 工作负载上也会产生较差的性能。

multi

每个核心最多使用两个并行解压缩程序

如果您有并行 I/O 工作负载并且您的系统有足够的内存,则使用此选项可能会提高整体 I/O 性能。它根据需求动态分配解压缩程序。

percpu

每个核心最多使用一个解压缩程序

它使用 percpu 变量来确保解压缩在核心之间进行负载平衡。

1|2|3|...

配置用于解压缩的线程数

上限为 num_online_cpus() * 2。

如果 SQUASHFS_CHOICE_DECOMP_BY_MOUNT 设置,并且 SQUASHFS_DECOMP_MULTI 和 SQUASHFS_MOUNT_DECOMP_THREADS 都已设置

2|3|...

配置用于解压缩的线程数

上限为 num_online_cpus() * 2。

3. Squashfs 文件系统设计

一个 squashfs 文件系统最多由九个部分组成,这些部分按字节对齐打包在一起

 ---------------
|  superblock   |
|---------------|
|  compression  |
|    options    |
|---------------|
|  datablocks   |
|  & fragments  |
|---------------|
|  inode table  |
|---------------|
|   directory   |
|     table     |
|---------------|
|   fragment    |
|    table      |
|---------------|
|    export     |
|    table      |
|---------------|
|    uid/gid    |
|  lookup table |
|---------------|
|     xattr     |
|     table     |
 ---------------

压缩数据块在从源目录读取文件时写入文件系统,并检查重复项。一旦写入所有文件数据,就会写入已完成的 inode、目录、片段、导出、uid/gid 查找和 xattr 表。

3.1 压缩选项

压缩器可以选择支持压缩特定选项(例如字典大小)。如果使用了非默认压缩选项,则将这些选项存储在此处。

3.2 Inode

元数据(inode 和目录)在 8K 字节的块中压缩。每个压缩块都以两个字节的长度为前缀,如果该块未压缩,则设置最高位。如果设置了 -noI 选项,或者如果压缩块大于未压缩块,则该块将未压缩。

Inode 被打包到元数据块中,并且不对齐到块边界,因此 inode 重叠压缩块。Inode 由一个 48 位数字标识,该数字对包含 inode 的压缩元数据块的位置以及 inode 放置在该块中的字节偏移量进行编码 (<块, 偏移量>)。

为了最大化压缩,每种文件类型(常规文件、目录、设备等)都有不同的 inode,inode 内容和长度随类型而变化。

为了进一步最大化压缩,定义了两种类型的常规文件 inode 和目录 inode:为频繁出现的常规文件和目录优化的 inode,以及必须存储额外信息的扩展类型。

3.3 目录

与 inode 一样,目录被打包到压缩的元数据块中,存储在目录表中。使用包含目录的元块的起始地址和解压缩块的偏移量 (<块, 偏移量>) 访问目录。

目录以稍微复杂的方式组织,而不仅仅是文件名列表。该组织利用了这样一个事实:文件的 inode(在大多数情况下)将位于同一个压缩元数据块中,因此可以共享起始块。因此,目录以两级列表组织,目录标头包含共享起始块值,以及一系列目录条目,每个条目共享共享起始块。一旦/如果 inode 起始块发生变化,就会写入新的目录标头。目录标头/目录条目列表会重复多次,直到需要为止。

目录已排序,并且可以包含目录索引以加快文件查找速度。目录索引存储每个元块一个条目,每个条目存储索引/文件名映射到每个元数据块中的第一个目录标头。目录按字母顺序排序,在查找时,索引会线性扫描,查找按字母顺序大于正在查找的文件名的第一个文件名。此时,已经找到了文件名所在的元数据块的位置。索引的总体思路是确保只需要解压缩一个元数据块即可进行查找,而不管目录的长度如何。该方案的优点是不需要额外的内存开销,并且不需要磁盘上的额外存储空间。

3.4 文件数据

常规文件由一系列连续的压缩块和/或压缩片段块(尾端打包块)组成。每个数据块的压缩大小存储在文件 inode 中包含的块列表中。

为了加快读取“大”文件(256 Mbytes 或更大)时对数据块的访问速度,代码实现了一个索引缓存,该缓存缓存从块索引到磁盘上的数据块位置的映射。

索引缓存允许 Squashfs 处理大文件(高达 1.75 TiB),同时在磁盘上保留一个简单且节省空间的块列表。缓存被分成多个槽,最多缓存八个 224 GiB 文件(128 KiB 块)。更大的文件使用多个槽,1.75 TiB 文件使用所有 8 个槽。索引缓存旨在节省内存,默认使用 16 KiB。

3.5 片段查找表

常规文件可以包含一个片段索引,该索引使用片段查找表映射到磁盘上的片段位置和压缩大小。此片段查找表本身以压缩形式存储到元数据块中。第二个索引表用于定位这些。为了加快访问速度(并且因为它很小),此第二个索引表在挂载时读取并缓存在内存中。

3.6 Uid/gid 查找表

为了提高空间效率,常规文件存储 uid 和 gid 索引,这些索引使用 id 查找表转换为 32 位 uids/gids。此表以压缩形式存储到元数据块中。第二个索引表用于定位这些。为了加快访问速度(并且因为它很小),此第二个索引表在挂载时读取并缓存在内存中。

3.7 导出表

为了使 Squashfs 文件系统可导出(通过 NFS 等),文件系统可以选择性地(使用 -no-exports Mksquashfs 选项禁用)包含一个 inode 编号到 inode 磁盘位置查找表。这是必需的,以便 Squashfs 将文件句柄中传递的 inode 编号映射到磁盘上的 inode 位置,这在导出代码重新实例化过期的/刷新的 inode 时是必需的。

此表以压缩形式存储到元数据块中。第二个索引表用于定位这些。为了加快访问速度(并且因为它很小),此第二个索引表在挂载时读取并缓存在内存中。

3.8 Xattr 表

xattr 表包含每个 inode 的扩展属性。每个 inode 的 xattr 存储在一个列表中,每个列表条目包含一个类型、名称和值字段。类型字段对 xattr 前缀(“user.”、“trusted.” 等)进行编码,并且还对应该如何解释名称/值字段进行编码。当前,该类型指示值是内联存储(在这种情况下,值字段包含 xattr 值)还是离线存储(在这种情况下,值字段存储对实际值的存储位置的引用)。这允许离线存储大值,从而提高扫描和查找性能,并且还允许对值进行去重,该值存储一次,所有其他出现都保存对该值的离线引用。

xattr 列表被打包到压缩的 8K 元数据块中。为了减少 inode 中的开销,而不是将 xattr 列表的磁盘上位置存储在每个 inode 中,而是存储一个 32 位 xattr id。此 xattr id 使用第二个 xattr id 查找表映射到 xattr 列表的位置。

4. TODO 和未解决的问题

4.1 TODO 列表

实现 ACL 支持。

4.2 Squashfs 内部缓存

Squashfs 中的块是压缩的。为了避免重复解压缩最近访问的数据,Squashfs 使用两个小的元数据和片段缓存。

该缓存不用于文件数据块,这些数据块以正常方式在页面缓存中解压缩和缓存。该缓存用于临时缓存由于元数据(即 inode 或目录)或片段访问而读取的片段和元数据块。由于元数据和片段被打包到块中(以获得更大的压缩),因此读取特定的元数据或片段将检索已与其打包的其他元数据/片段,由于引用局部性,这些元数据/片段可能在不久的将来被读取。临时缓存它们可确保它们可用于不久的将来的访问,而无需额外的读取和解压缩。

将来,此内部缓存可能会被使用内核页面缓存的实现所取代。由于页面缓存以页面大小的单元运行,这可能会在锁定和相关的竞争条件方面引入额外的复杂性。