背景故事在我之前的问题中我自己的答案。
有一次我创建了两个部分图像ddrescue
:一个文件在 NTFS 文件系统上,另一个在 ext4 上。
我很早就注意到,两个映像的“磁盘大小”远低于总大小,这表明(如果我没记错的话)这些文件已被写入“稀疏”,即空数据实际上并未分配到相应的卷上,只有已恢复的数据才被考虑在内。但我从未-S
在ddrescue
命令中使用过开关,该开关指定输出文件应写入“稀疏”。
旁注:我所做的是-R
在开始时使用开关(“反向”),认为它会立即分配输入硬盘的整个大小(想法是它将产生一个“更干净”的输出,将所有数据按顺序写入接收分区,以便即使文件系统出现问题并且我必须恢复恢复,也能保持映像文件的完整性……);它确实将文件的显示大小增加到 931.5GB,但实际上“磁盘上的大小”仅增加了在此步骤中复制的少量数据。
因此主要问题是:如何解释这种稀疏性?为什么ddrescue
默认情况下复制不是连续的?
然后,由于我有两张部分图像,每张都包含另一张所缺少的一些有效数据,因此我执行以下操作:
- 我尝试将 ext4 分区上第二个映像中缺失的挽救区域复制到 NTFS 分区上的第一个映像中,这两个映像位于同一个健康的 2TB 硬盘上(Seagate ST2000DX001,最大写入速度接近 200MB/s),速度应该非常快。但结果发现速度非常慢:只有 660KB/s。
- 于是我停下来,做了相反的事情:我将
ddrescue
第一个映像(在 NTFS 上)中挽救的区域(第二个映像中缺失的区域)复制到第二个映像(在 ext4 上)。现在我得到的复制速率为 43000KB/s 或 43MB/s,这要高得多,更接近该级别和容量的同一硬盘内的正常复制速率。
第二个问题:这种奇怪的行为是否与我在写入 NTFS 时遇到的性能问题有关?Linux NTFS 驱动程序是否已知在处理大型“稀疏”文件时会遇到麻烦?
答案1
这个答案调查了行为ddrescue
以解决主要问题。如果你对测试过程不感兴趣,那么你可以直接跳到结尾处的我的结论和解释。
测试平台
$ uname -a
Linux foo 4.2.0-27-generic #32~14.04.1-Ubuntu SMP Fri Jan 22 15:32:26 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
$ cat /etc/issue
Ubuntu 14.04.5 LTS \n \l
$ ddrescue -V
GNU ddrescue 1.17
…
文件系统是 btrfs;只要它支持稀疏文件,那就没关系。
测试
首先我得到了8MiB的随机数据:
dd if=/dev/urandom of=random.chunk bs=1M count=8
然后我把它做成一个环回设备并记住了它的名字:
loopdev=`sudo losetup -f --show random.chunk`
接下来我又制作了另一个装置,它由
- 块 0:不可读,1 MiB
- 块 1:零,2 MiB
- 块 2:不可读,4 MiB
- 块 3:来自的数据
random.chunk
,8 MiB - 块 4:不可读,16 MiB
代码(它使用这里的文件句法):
sudo dmsetup create mydevice << EOF
0 2048 error
2048 4096 zero
6144 8192 error
14336 16384 linear $loopdev 0
30720 32768 error
EOF
我确认gdisk -l /dev/mapper/mydevice
总大小应该是 31 MiB。
实际读取是通过以下方式完成的:
ddrescue /dev/mapper/mydevice normal.raw normal.log
ddrescue -R /dev/mapper/mydevice normalR.raw normalR.log
ddrescue -S /dev/mapper/mydevice sparse.raw sparse.log
ddrescue -RS /dev/mapper/mydevice sparseR.raw sparseR.log
结果ls -hls *.raw
是
10M -rw-rw-r-- 1 kamil kamil 15M Sep 10 00:37 normal.raw
10M -rw-rw-r-- 1 kamil kamil 15M Sep 10 00:37 normalR.raw
8.0M -rw-rw-r-- 1 kamil kamil 15M Sep 10 00:37 sparse.raw
8.0M -rw-rw-r-- 1 kamil kamil 15M Sep 10 00:37 sparseR.raw
为确保万无一失,我确认了cmp
这四个文件在读取时是完全相同的。四个日志文件包含相同的错误扇区和健康扇区图。
请注意
- 15 MiB 表示最后一个块丢失;
- 10MiB表示块1和块3;
- 8MiB仅表示块3。
打扫
sudo dmsetup remove mydevice
sudo losetup -d $loopdev
unset loopdev
rm random.chunk normal.raw normal.log normalR.raw normalR.log sparse.raw sparse.log sparseR.raw sparseR.log
结论
- 当谈到文件大小时,是否反向读取(
-R
)并不重要。 - 输入文件最末端的不可读块不会影响输出文件的整体大小。
- 不可读的块确实会影响整个文件的大小,但它们总是很稀疏(当然,如果目标文件系统支持这一点)。
- 该
-S
选项仅影响从输入文件中实际读取的零块。
解释
以上是事实。这部分更像是我的观点。
ddrescue
只要无需额外工作,它似乎就会尽力为您节省磁盘空间。当您使用-S
该工具时,必须进行一些计算来检查给定的数据块是否全为零。如果出现读取错误,它不需要计算任何内容,它可以使输出文件中的片段稀疏,而无需任何成本。
解决方案
你写了:
在开始时使用
-R
开关(“反向”),认为它会立即分配输入硬盘的整个大小
我们刚刚看到这是一个错误的假设。事实上,你描述了什么-p
是错误的。ddrescue -p
将为输出文件预先分配磁盘空间。当我在测试期间执行此操作时,输出文件有 31 MiB 并且并不稀疏(即使有-S
)。
答案2
我自己做了一个不同的测试。
– 我创建了一个简单的模板 ddrescue 日志/映射文件,其中包含以下内容:
0x00000000 0x100000 ?
0x100000 0x3FE00000 +
0x3FF00000 0x100000 ?
(这意味着:在总共 1GB 的数据中,第一个和最后一个 MB 还没有被尝试,其余的被视为“已拯救”。)
– 我使用该日志/映射文件运行了 ddrescue,使用以下命令(以从该 1TB HDD 的恢复中拯救出来的图像作为输入,将输出切割为 1GB):
ddrescue -s 1073741824 [rescued_image_file] [test1GB] [test1GB.log]
生成的 [test1GB] 文件的总大小正如预期的那样为 1GB,但“磁盘大小”为 2MB,这意味着只分配了实际复制的数据(第一个和最后一个 MB)。
– 然后我以该 1GB 文件作为输入运行 ddrescue,这次没有使用模板,先不使用模板,然后使用 -S 开关(“稀疏写入”)。
ddrescue [test1GB] [test1GB-NS] [test1GB-NS.log]
ddrescue -S [test1GB] [test1GB-S] [test1GB-S.log]
看起来:
- [test1GB-NS](非稀疏)的“磁盘大小”为 1GB - 因此整个文件都已被分配和复制,甚至空扇区也是如此;然而...
- [test1GB-S](稀疏)的“磁盘大小”仅为 1.2MB 或 1114112 字节 - 这意味着尚未分配空扇区,即使是包含在第一个和最后一个 MB 中的扇区也是如此。
我认为“稀疏性”是一个全有或全无的概念,就像文件压缩一样,但显然存在“部分稀疏”文件这样的东西,而且 ddrescue 确实似乎通过这种方式节省了空间 - 这不一定是一个优势(并且可能确实会对性能产生影响);应该有一个开关让它动态分配输出文件的全部大小(而不是预先分配,如果输入很大,这可能会很长),就像它(显然)直接写入设备或分区时一样。