问题场景
我们使用 rsync 做文件同步。
问题现象
迁移完成后,目的端的磁盘分区已使用大小与源端不一致,相差比较大。
迁移完成后,目的端文件大小与源端文件大小不一致。
问题原因
当源端存在空洞文件时,迁移后目的端的文件大小可能比源端要小,这是因为 rsync 传输空洞文件时,会自动处理空洞文件。当数据块所有字节都为 0 时,并不会写入磁盘,而是会生成一个空洞,这样目的端文件的实际大小就可能比源端的要小。
这里提到了一个概念 - 文件空洞,大家很多新接触 Linux 的童鞋肯定一脸黑人问号,那我们就来揭开这个基础知识吧。
文件空洞
什么是空洞文件(hole file)?在 Linux (Unix)中,lseek的系统调用是可以改变在文件上面的偏移量的,而且还允许其超出文件的长度。偏移量一旦超出了文件的长度,下一次进行文件IO写入操作文件的时候便会延续偏移量的位置继续写入,进而在文件中间产生了空洞的部分,这部分会以”\0”填充,而从原来的文件结尾到新写入数据间的这段空间就被称为“文件空洞”。
在Linux中,EOF(文件结束符)并不是一个字符,而是在读取到文件末尾的时候返回的一个信号值,也就是-1。
文件中的空洞并不要求在磁盘上占用存储区。具体处理方式与文件系统的实现有关,当定位到超出文件尾端之后写时,对于新写的数据需要分配分配磁盘块,但是对于原文件尾端和新开始写位置之间的的部分(即空洞部分)则不需要分配磁盘块。
空洞文件的存在意味着一个文件实际上占用的磁盘大小是可能会小于该文件名义上的大小的。
空洞文件的作用
空洞文件看上去好像是一个不太靠谱不太安全的操作,但其实在很多情况都很有用:
像在我们平时使用迅雷下载的时候,刚开始下载但是本地的下载文件就已经好几百兆了,这就利用了空洞文件。为了能够并行下载,创建空洞文件可以让多线程在不同的 seek 上面开始写入文件,如果不是空洞文件就只能串行写入了。
在创建虚拟机的时候,我们会使用 img 工具生成一个例如 50GB 大小的镜像文件,但是其实在安装完系统之后,镜像的大小可能只有 4GB,也就是说 img 并不会马上就占用掉物理存储空间的 50GB,而是在未来使用过程中不断增加的
空洞文件的一个常见用途是在数据库等应用中预分配磁盘空间,以便将来可以向其中写入数据而无需频繁扩展文件大小。
参考
实验
接下来可以通过一个实验来验证空洞文件的形成,并且使用 cat 和 cp 两种方式对空洞文件进行操作。
首先我们使用 dd 命令产生一个空洞文件。
dd if=/dev/urandom of=hole.file bs=4096 seek=999 count=11+0 records in1+0 records out4096 bytes (4.1 kB, 4.0 KiB) copied, 0.000449329 s, 9.1 MB/s
参数说明
if - 输入文件
of - 输出文件
seek - 设置输出文件的偏移量
skip - 设置输入文件的偏移量
bs - 是ibs和obs的合集
使用 ls 命令查看文件大小:
ls -lh hole.file-rw-r--r-- 1 root root 4.0M 4月 17 15:12 hole.file
使用 du 命令查看文件大小:
du -h hole.file4.0K hole.file
哇,逻辑大小和实际大小存在如此大的差别!
接着我们可以使用 od 命令来查看 hole.file 文件的二进制内容:
od -c hole.file0000000 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0...
会看到其实整个 hole.file 文件的开始是用许多的 ”\0” 来填充的,由于上面设置了偏移量比较大,可以设置小一点的 seek 来观察。
我们使用 cat 来重定向文件到一个新的文件:
cat hole.file > hole.cat
再使用 cp 命令将文件拷贝一份出来:
cp hole.file hole.bak
使用 ls 命令查看两个新生文件的大小:
ls -lh hole.cat hole.cp-rw-r--r-- 1 root root 4.0M 4月 17 15:39 hole.cat-rw-r--r-- 1 root root 4.0M 4月 17 15:40 hole.bak
再使用 du 查看两个新生文件的大小:
du -h hole.cat hole.bak4.0M hole.cat4.0K hole.bak
总结
为什么 ls 和 du 命令得到的文件大小会相差如此巨大呢?是因为 ls 获取得到的是文件的逻辑大小,而 du 获取得到的是文件的实际占用物理块的大小。也就是说,当产生空洞文件的时候,文件系统并不会将空洞文件部分对应也分配好空间,那样是相当浪费的,而且还会被一些黑客利用,在系统中产生大量的空洞文件来耗尽系统的存储资源。
注意:ls -sh 列出来的大小和 du 出来的大小是一样的,所以可以认为 ls -s 所获取得到的是文件实际占用的存储块的大小。
cat 命令重定向内容到新的文件的时候,其遇到了空洞部分,会用 0 来填充,这样空洞部分其实也就有了内容了,对应需要为他分配物理存储 block,这样文件的真实大小其实就是 4MB,但是 cp 命令在遇到空洞部分的时候,会模拟源文件的空洞调用 seek,进行偏移之后再进行内容拷贝,这样其实生成的新文件和源文件是一样的,空洞部分都是不被分配真是存储空间的。
命令总结
ls -l file 查看文件逻辑大小
du -c file 查看文件实际占用的存储块多少
od -c file 查看文件存储的内容
相关概念
稀疏文件(sparse file):文件有空洞的通常被称为稀疏文件(Files with holes are usually referred to as sparse files)。