我有一个包含超过 400 GiB 数据的目录。我想检查所有文件是否可以正确读取,所以我想到的一个简单方法是将tar
其放入/dev/null
. 但相反,我看到了以下行为:
$ time tar cf /dev/null .
real 0m4.387s
user 0m3.462s
sys 0m0.185s
$ time tar cf - . > /dev/null
real 0m3.130s
user 0m3.091s
sys 0m0.035s
$ time tar cf - . | cat > /dev/null
^C
real 10m32.985s
user 0m1.942s
sys 0m33.764s
Run Code Online (Sandbox Code Playgroud)
上面的第三个命令在运行了很长时间后被Ctrl+强行停止C。此外,当前两个命令正在工作时,包含的存储设备的活动指示器.
几乎总是空闲的。使用第三个命令时,指示灯会持续亮起,表示非常忙碌。
所以看起来,当tar
能够发现它的输出文件是 时/dev/null
,即当/dev/null
直接打开有tar
写入的文件句柄时,文件体似乎被跳过了。(添加v
选项tar
确实将目录中的所有文件打印为tar
“红色”。)
所以我想知道,为什么会这样?这是某种优化吗?如果是,那么为什么还要tar
对这种特殊情况进行如此可疑的优化?
我在 Linux 4.14.105 amd64 上使用 GNU tar 1.26 和 glibc 2.27。
mur*_*uru 25
这是 一个记录在案的优化:
当存档被创建到 时
/dev/null
,GNU tar 会尽量减少输入和输出操作。Amanda 备份系统与 GNU tar 一起使用时,具有使用此功能的初始大小调整通道。
这可能发生在各种程序中,例如,我在使用cp file /dev/null
;时曾经有过这种行为;该命令在几毫秒后返回,而不是估计我的磁盘读取速度。
据我所知,那是在 Solaris 或 AIX 上,但该原理适用于各种 unix-y 系统。
在过去,当程序将文件复制到某个地方时,它会在read
从磁盘(或文件描述符所指的任何内容)获取一些数据到内存(保证read
返回时一切都在那里)的write
调用和调用之间交替(获取内存块并将内容发送到目的地)。
但是,至少有两种较新的方法可以实现相同的目标:
Linux 有系统调用copy_file_range
(根本不能移植到其他 unix)和sendfile
(有点可移植;最初打算将文件发送到网络,但现在可以使用任何目的地)。它们旨在优化传输;如果程序使用其中之一,很容易想象内核会识别目标/dev/null
并将系统调用转换为无操作
程序可以mmap
用来获取文件内容而不是read
,这基本上意味着“当我尝试访问该内存块时确保数据在那里”而不是“当系统调用返回时确保数据在那里”。因此,程序可以mmap
访问源文件,然后调用write
该映射内存块。但是,由于写入/dev/null
不需要访问写入的数据,因此不会触发“确保它在那里”条件,从而导致文件也未被读取。
不确定 gnu tar 在检测到它正在写入时是否使用这两种机制中的任何一种,以及这两种机制中的哪一种/dev/null
,但它们是为什么任何程序在用于检查读取速度时都应该使用| cat > /dev/null
而不是> /dev/null
- 以及为什么| cat > /dev/null
应该运行的原因在所有其他情况下都应避免。