我有一个目录,其中包含超过 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
上面的第三个命令在运行了很长时间后被Ctrl+强行停止了。C此外,当前两个命令工作时,包含的存储设备的活动指示器.
几乎总是处于空闲状态。使用第三个命令时,指示灯会持续亮起,表示极度忙碌。
所以看来,当tar
能够查出它的输出文件是/dev/null
,即/dev/null
直接打开有tar
写入的文件句柄时,文件体就出现了跳过。(添加v
选项以tar
打印目录中的所有文件为tar
“红色”。)
所以我想知道,为什么会这样?是某种优化吗?如果是的话,那为什么还要tar
对这种特殊情况进行如此可疑的优化呢?
我在 Linux 4.14.105 amd64 上使用 GNU tar 1.26 和 glibc 2.27。
这是 一个记录在案的优化:
这可能发生在各种程序中,例如,我曾经在使用
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
应该在所有其他情况下都应避免。