使用完整文件系统代替传统压缩格式
前言
Squashfs是一种只读文件系统,广泛应用于嵌入式、发行版镜像等场景,也可用于一般的备份或归档使用。相较于常见的zip
、7z
、rar
、tar.gz
等压缩格式,Squashfs的主要优势是它是一个完整的文件系统,在Linux下可以直接挂载使用,而不需要解压缩。这也意味着,Squashfs也可以作为独立的镜像直接传递给虚拟机等使用,而不需要更多的操作。
特点 | 传统压缩文件 | Squashfs |
---|---|---|
类型 | 归档压缩文件 | 完整文件系统 |
读取方式 | 解压缩后读取 | 直接挂载读取 |
随机访问支持 | 支持较差 | 支持相对较好 |
是否可作为块设备 | 否 | 是 |
同时,Squashfs在使用合适的压缩算法及分块大小时,能够兼有高压缩率与高访问速度;Squashfs还支持文件级别重复数据删除,进一步节省空间。
Squashfs的支持也非常广泛,几乎所有的Linux发行版都支持Squashfs,而Windows和macOS也都有Squashfs的解压工具;对于zstd
等相对较新的算法,在其他平台下打开采用对应压缩方式的Squashfs则可能需要安装额外的软件,例如7-Zip-zstd。
基础用法
Squashfs支持多种压缩算法,包括:
gzip
:-comp gzip
lzo
:-comp lzo
lz4
:-comp lz4
xz
:-comp xz
zstd
:-comp zstd
在不追求极致压缩率的场景下,笔者推荐使用zstd:3
的压缩算法,这样可以节省空间,而且压缩速度非常快、CPU占用很小。根据zstd
算法的特性,使用较高压缩等级的zstd
算法可以在牺牲一定创建压缩的速度的情况下获得更高的压缩率,但是并不会明显降低透明的解压缩的速度,因此,如果需要更高的压缩率,可以考虑使用更高等级的zstd
压缩算法,例如将压缩等级设置为6
或者15
。
笔者个人不太推荐使用解压性能相对较差的gzip
、xz
等算法,因为这些算法的压缩率提升相较于zstd
有限,而且解压速度较慢,不适合创建后的快速访问。
如果需要比zstd
更快的透明访问/解压速度,可以考虑使用lz4
算法,lz4
的解压速度非常快,但是在实际场景中,zstd
的速度其实也已经足够快了,而且在压缩率上一般还有一定的优势。(PS. zstd
和lz4
的作者其实是同一个人)
需要注意的是,mksquashfs
的基本命令格式为:
mksquashfs source1 source2 ... FILESYSTEM [OPTIONS]
[OPTIONS]
必须放到FILESYSTEM
之后,否则会报错。
文件/目录级归档
如果需要归档已经挂载的文件系统上的文件,可以直接使用mksquashfs
,例如:
# 归档 /home 目录
sudo mksquashfs /home /XXX/example/backup.sfs -comp zstd -Xcompression-level 3
# 归档 / 目录
sudo mksquashfs / /XXX/example/backup.sfs -comp zstd -Xcompression-level 3 -e /dev -e /proc -e /sys -e /tmp -e /run -e /mnt -e /media -e /lost+found -e /boot/efi -e /efi
# 归档 / 子卷
sudo mksquashfs / /XXX/example/backup.sfs -comp zstd -Xcompression-level 3 --one-file-system
这里只是用于演示较彻底的归档备份,实际使用中如果只是一般归档,往往并不需要sudo
权限,例如:
# 归档 abc 目录
mksquashfs abc /XXX/example/backup.sfs -comp zstd -Xcompression-level 3
像这样创建的squashfs是一个直接包含归档文件的文件系统,可以直接挂载使用。
块设备级备份
有时候我们可能需要备份某些在Linux下不方便直接挂载并备份的分区,比如受BitLocker加密的NTFS分区等,这时,为了完成保留整个硬盘设备的信息,我们可以直接备份整个硬盘设备。如果需要备份整个硬盘设备,可以使用dd
和mksquashfs
结合,即,用mksquashfs
调用dd
的输入流,例如:
sudo mksquashfs - /XXX/example/backup.sfs -p "backup.img f 0644 root root dd if=/dev/nvme0n1 bs=1M" -p "/ d 0755 0 0" -comp zstd -Xcompression-level 3
此处:
-
表示标准输入流-p
表示添加文件/目录-p
后面的内容是mksquashfs
的文件描述符f
表示类型为文件- 后面紧跟的是权限信息
dd if=/dev/nvme0n1 bs=1M
表示调用dd
的输入流if=/dev/nvme0n1
表示输入文件bs=1M
表示块大小
/
表示根目录d
表示类型为目录- 后面同样跟有权限信息。
整体上,这个命令的意思是:
- 需要在squashfs中创建根目录与
backup.img
文件 - 将
/dev/nvme0n1
的输入流作为backup.img
文件的内容 - 将
backup.img
文件放在创建的squashfs
文件系统的根目录
这样既保留了整个硬盘的完整存储信息,又启用压缩,节省了空间。不过,从这样的备份中读取内容时,需要先挂载squashfs
文件系统,然后加载其中的backup.img
文件为loop设备,再挂载所需要挂载的分区。(套娃)
利用pipe进行的其他备份
如前一节所示,mksquashfs
支持从标准输入流读取数据,而且由于从一般的流输入通常需要另外指定权限信息与文件名等内容,从流中读取数据的用法和一般的程序形式[command A] | [command B]
不同,可以归纳为:
mksquashfs - [FILESYSTEM] -p "filename f [mod] owner owner [command A]" -p "/ d [mod] owner owner" [OPTIONS]
除了dd
外,我们还可以使用其他命令,例如,我们可以将btrfs send [subvolume name]
的输出流作为mksquashfs
的输入流,这样可以将btrfs
的子卷快照备份为Squashfs:
mksquashfs - /xxx/example/backup.sfs -p "xyz.btrfs f 0644 0 0 sudo btrfs send /path/to/your/ro/subvolume" -p "/ d 0755 0 0" -comp zstd -Xcompression-level 10
将其他归档压缩格式转换为Squashfs
有时候,我们现有的归档压缩文件可能并不是Squashfs,而是其他格式,例如7z
、tar.gz
、tar.xz
、zip
等,这时,我们为了得到既节约空间,又不用解压缩就可以直接挂载使用的归档文件,往往需要将这些归档文件转换为Squashfs。
最容易想到的方法是:
- 解压缩归档文件
- 随后使用
mksquashfs
创建Squashfs
然而,这样做会解压产生很多中间文件,占用大量的空间,而且需要花费大量的IO时间。
笔者在这里推荐充分利用管道,直接将归档文件的解压缩输出作为mksquashfs
的输入流,在不产生中间文件的情况下,将归档文件转换为Squashfs。
转化tar.xxx
到Squashfs
mksquashfs
工具支持从标准输入流读取数据:
- 用
-
代表标准输入流stdin
- 用
-tar
参数指定从标准输入流读取tar格式的数据 - 使用相应压缩软件
gz
、zstd
等的输出作为mksquashfs
的输入流
例如,小米发布的线刷包是一个tar.gz
格式的归档文件,在使用时,一般需要先解压,然而,这势必会浪费大量的时间。而如果直接在本地存储解压后的文件,又会浪费大量的空间。因此,我们可以使用以下命令,将tar.gz
格式的线刷包转换为Squashfs,既保证了压缩率,又不需要额外的解压缩过程:
gzip -cd zircon_images_V14.0.8.0.TNOCNXM_20231129.0000.00_13.0_cn_f63a143fa2.tgz | mksquashfs - zircon_images_V14.0.8.0.TNOCNXM_20231129.0000.00_13.0_cn_f63a143fa2.sfs -tar -p "/ d 0755 0 0" -comp zstd -Xcompression-level 6
gzip -cd
表示解压缩(-c
参数)tar.gz
文件并输出到标准输出流stdout
(-d
参数)|
表示管道mksquashfs -
表示将标准输入流作为mksquashfs
的输入流-tar
表示从标准输入流读取未压缩的tar数据-p
表示定义Squashfs中的文件组织/ d 0755 0 0
表示指定Squashfs文件系统的根目录d
表示类型为目录,后面跟有权限信息-comp zstd -Xcompression-level 6
表示使用zstd
压缩,压缩等级为6
另外需要注意的是,解压命令需要与原压缩包的压缩格式相匹配,例如,如果原压缩包是tar.xz
格式,那么解压命令应该是xz -cd
,如果原压缩包是tar.zst
格式,那么解压命令应该是zstd -cd
。
当然,对于本身即未压缩的tar数据,我们也可以直接使用mksquashfs
的-tar
参数,直接用cat
传入即可,例如:
cat example.tar |mksquashfs - example.sfs -tar -comp zstd -Xcompression-level 6
此外,如果本地还没有下载tar.xxx
格式的归档文件,我们也可以直接使用wget
的输出流进行转化,例如:
wget -O - https://example.com/example.tar.gz | gzip -d | mksquashfs - example.sfs -tar -comp zstd -Xcompression-level 6
这样,我们就在不产生中间文件的情况下,将远程的tar.xxx
格式的归档文件转换为了Squashfs,可以直接挂载使用。
转化zip
到Squashfs
由于mksquashfs
工具可以高效地从标准输入流stdin
中读取未压缩的tar数据,我们可以用类似的方法处理zip
格式的归档文件,但细节上有所不同。
一般的zip
归档文件通常与tar.xxx
不同,它的归档与压缩均由zip
完成,内部并不含有未压缩的tar数据。因此,我们在这里不能直接使用unzip
的输出流进行管道操作。
幸运的是,bsdtar
这一强大的工具可以直接读取zip
格式的归档文件(只需要在文件路径前面加上@
),并将其转化为tar流,通过管道正常传输给mksquashfs
。因此,我们可以使用以下命令,将zip
格式的归档文件转换为Squashfs:
bsdtar -c @test.zip | mksquashfs - test.sfs -tar -p "/ d 0755 0 0" -comp zstd -Xcompression-level 6
bsdtar -c @test.zip
表示将test.zip
文件转换为tar流并输出到标准输出流stdout
|
表示管道mksquashfs -
表示将标准输入流作为mksquashfs
的输入流-tar
表示从标准输入流读取未压缩的tar数据-p
表示定义Squashfs中的文件组织/ d 0755 0 0
表示添加根目录d
表示类型为目录,后面跟有权限信息-comp zstd -Xcompression-level 6
表示使用zstd
压缩,压缩等级为6
bsdtar
直接读取归档的特性不仅仅适用于zip
格式的归档文件,还适用于tar
, pax
, cpio
, jar
, ar
, xar
, rpm
, 7z
, ISO 9660 CDROM
,以上格式均可利用类似方法转换为Squashfs。
Squashfs到Squashfs的转化
有时候,我们可能需要更换Squashfs的压缩算法、块大小、压缩等级等参数,这时,我们必须要另外创建新的Squashfs。
最简单的思路是:
- 挂载现有Squashfs
- 对挂载目录使用
mksquashfs
创建新的Squashfs
但是,这样的操作其实是在随机访问已挂载的Squashfs,IO性能可能较差。如果需要一次性访问Squashfs中的所有文件,先挂载再访问的方式一般会比使用unsquashfs
慢。
然而,如果直接使用unsquashfs
解压缩Squashfs,又会产生大量的中间文件,占用大量的空间,而且需要花费大量的IO时间。
这里我们同样可以使用管道,将unsquashfs
的输出作为mksquashfs
的输入流,在不产生中间文件的情况下,将Squashfs转换为新的Squashfs。例如:
unsquashfs -pf - source.sfs | mksquashfs - output.sfs -pf - -comp zstd -Xcompression-level 6 -b 16K
unsquashfs -pf - source.sfs
表示将source.sfs
文件解压缩到标准输出流stdout
- 同时也一并输出了文件组织的结构、权限等信息
|
表示管道,mksquashfs -
表示将标准输入流作为mksquashfs
的输入流-pf -
表示从标准输入流读取文件组织的结构、权限等信息-comp zstd -Xcompression-level 6
表示使用zstd
压缩,压缩等级为6
-b 16K
表示块大小为16K
由此,我们在不产生中间文件的情况下,利用unsquashfs
更优秀的IO性能,将原来的Squashfs快速转换为了新的Squashfs,并重新指定了压缩算法、块大小、压缩等级等参数。
展望
Squashfs是一个非常实用的只读文件系统,然而,这一文件系统创建于2009年,今天看来它的某些特性已经不是那么合适:Squashfs对随机访问的性能仍然有很大的提升空间。
EROFS是一个较新的只读文件系统,已广泛应用于Android的系统分区,它特别针对读取性能进行了优化,对随机访问有更好的支持。
- Squashfs文件系统最多由九个部分组成
- 超级块
superblock
- 压缩类型
compression options
- 数据块/片段
datablocks & fragments
- 索引节点表
inode table
- 目录表
directory table
- 片段表
fragment table
- 导出表
export table
- uid/gid查找表
uid/gid lookup table
- 扩展属性表
xattr table
- 超级块
- 这些部分按照字节对齐,结构紧凑
- 数据块并没有对齐,不利于随机访问
访问未对齐的数据块往往会被迫读取多个数据块,造成额外的IO开销。而注重随机访问性能的一般文件系统,例如ext4、XFS、Btrfs、F2FS等,都会对数据块进行块对齐,以减少随机访问时的IO开销。EROFS也默认采用了块对齐的设计,对随机访问有更好的支持。
Squashfs等未采用块对齐的文件系统与EROFS等采用块对齐的文件系统设计对比 |
块对齐的优势在于提高了I/O操作的效率,减少了不必要的内存占用,增强了数据缓存的灵活性和效率,特别是在内存有限的设备上。
此外,EROFS还支持了Squashfs所不支持的块级去重、ACL等功能:1
Feature (as of Linux 6.6) | EROFS | SquashFS |
---|---|---|
Minimal block size | 512 B | Unaligned |
Inode size | 32/64 B | Varied |
Limitation of total UIDs/GIDs | No | Yes (64k) |
Pre-1970 / ns timestamps | Yes | No |
Filesystem UUID | Yes | No |
Filesystem label (Volume label) | Yes | No |
Inline data | Yes | No |
Data compression | Yes | Yes |
Largest compression granularity | 1 MiB | 1 MiB |
Default compression granularity | 1 Block | 128 KiB |
Fragments | Yes | Yes |
Metadata compression | No | Yes |
Multiple compression algorithms | Per-file | No |
Data deduplication | Extent-based | File-based |
Extended attribute support | Yes | Yes |
File-based distribution | Yes | No |
External data (multi-devices) | Yes | No |
POSIX.1e ACL support | Yes | No |
Direct I/O support | Yes | No |
FIEMAP support | Yes | No |
FSDAX support | Yes | No |
Large folio support | Yes | No |
然而,由于EROFS社区开发者有限,且主要开发力量集中于内核空间,以及主要服务于Android的向前移植,目前EROFS的用户空间程序(EROFS文件系统制作程序)完善程度仍然不足:
- 一切操作,包括IO、压缩等均在一个线程中完成,创建很慢
- 块级去重仍为实验性功能
- 去重的速度太慢
用户空间程序的不完善,使得EROFS在日常备份应用中的普及受到了一定的限制。不过,随着EROFS的不断发展,相信它会逐渐成为Squashfs的替代品。