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
|
||||||||||
threads=%s |
选择解压缩模式或线程数 如果设置了 SQUASHFS_CHOICE_DECOMP_BY_MOUNT
如果 SQUASHFS_CHOICE_DECOMP_BY_MOUNT 未设置,并且 SQUASHFS_DECOMP_MULTI 和 SQUASHFS_MOUNT_DECOMP_THREADS 都已设置
|
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 或目录)或片段访问而读取的片段和元数据块。由于元数据和片段被打包到块中(以获得更大的压缩),因此读取特定的元数据或片段将检索已与其打包的其他元数据/片段,由于引用局部性,这些元数据/片段可能在不久的将来被读取。临时缓存它们可确保它们可用于不久的将来的访问,而无需额外的读取和解压缩。
将来,此内部缓存可能会被使用内核页面缓存的实现所取代。由于页面缓存以页面大小的单元运行,这可能会在锁定和相关的竞争条件方面引入额外的复杂性。