以 100MB 原始块设备为例。即 204800 个块,每个块 512 字节,总共 102760448 字节。
挑战是移动前 98MB(200704 个块),使其前面有 2MB(4096 个块)的间隙。要就地执行此操作,需要不对尚未读取的扇区写入任何内容。实现此目的的一种方法是引入缓冲区:
$ dd if=/dev/sdj2 count=200704 | mbuffer -s 512 -b 4096 -P 100 | dd of=/dev/sdj2 seek=4096
预期mbuffer
在将任何内容传递给写入器之前将存储 4096 个块,从而确保不会将任何内容写入尚未读取的区域,并且写入器落后于读取器缓冲区的大小。缓冲区应允许读取器和写入器在这些约束内尽可能快地操作。
然而,它似乎工作并不可靠。我尝试过使用真实设备,但它永远无法在它们上运行,而使用文件进行的实验可以在我的 64 位机器上运行,但不能在我的 32 位机器上运行。
首先,一些准备工作:
$ dd if=/dev/sdj2 count=200704 | md5sum
0f0727f6644dac7a6ec60ea98ffc6da9
$ dd if=/dev/sdj2 count=200704 of=testfile
这不起作用:
$ dd if=/dev/sdj2 count=200704 | mbuffer -s 512 -b 4096 -P 100 -H | dd of=/dev/sdj2 seek=4096
summary: 98.0 MiByte in 4.4sec - average of 22.0 MiB/s
md5 hash: 3cbf1ca59a250d19573285458e320ade
这适用于 64 位系统,但不适用于 32 位系统:
$ dd if=testfile count=200704 | mbuffer -s 512 -b 4096 -P 100 -H | dd of=testfile seek=4096 conv=notrunc
summary: 98.0 MiByte in 0.9sec - average of 111 MiB/s
md5 hash: 0f0727f6644dac7a6ec60ea98ffc6da9
如何才能可靠地做到这一点?
笔记
我读过有关缓冲的其他问题并查看了pv
,buffer
和mbuffer
。我只能让后者使用所需的缓冲区大小。
使用中间存储是解决这个问题的一个明显的解决方案,它始终有效,但当没有足够的备用容量时,它就不实用。
运行 Arch Linux 版本 20140302 的测试平台mbuffer
。
答案1
如果没有缓冲区,您可能会一次向后退一个块。
for i in $(seq 100 -1 0)
do
dd if=/dev/thing of=/dev/thing \
bs=1M skip=$i seek=$(($i+2)) count=1
done
请注意,由于缺乏错误检查,此示例很危险。
由于呼叫量大,速度也很慢dd
。如果您有空闲内存,则可以使用更大的块大小。
有了缓冲器,谨防陷阱。这是不是足以保证 100% 预填充。您需要的是整个过程中的最小填充量。缓冲区绝不能低于以下值2M
,否则您将再次覆盖尚未读取的数据。
因此,理论上您可以不使用任何类型的缓冲区而只使用链dd
:
dd if=/dev/thing bs=1M | \
dd bs=1M iflag=fullblock | \
dd bs=1M iflag=fullblock | \
dd of=/dev/thing bs=1M seek=2
在实践中,这确实不是可靠地工作,因为不能保证第一个dd
设法继续读取数据,而最后一个dd
(2M
中间有“缓冲区”)已经在写入。
您可以通过使中间缓冲区变得相当大来大大增加机会,但即便如此,它仍然不可靠。
不幸的是,我不知道具有最小填充属性的良好缓冲程序。只要缓冲区内的安全裕度小于安全裕度,您就需要一种能够停止输出的设备。
答案2
您正在读取 4096 个块,然后将这 4096 个块写入磁盘的下一个 4096 个块,从而在读取第二个 4096 个块之前覆盖它们。在开始任何写入之前,您需要读取 8129 个块才能获得第二个 4096,然后在读取下一个 4096 之前只需要写入 4096 个块。
您没有提到这是什么类型的文件系统。如果它是 ext[234],并且您有最新版本的 e2fsprogs,那么您可以使用e2image -ra -O 512 /dev/sdj2
.这还有一个额外的好处,那就是足够智能,可以跳过卷中的可用空间。
答案3
可靠的解决方案要求您确保不会向可能未被读取的区域写入任何内容,而实现这一点的唯一真正方法是沿相反方向执行复制。
该ddrescue
工具可以反向工作,但它拒绝在输入和输出相同的情况下运行。然而,可以通过复制设备节点来欺骗它。
我已经进行了一些快速实验,它似乎有效。命令行是:
$ ddrescue -f -R -s 200704s -o 4096s /dev/sdj11 /dev/sdj11_copy
论据是
-f
需要强制它写入现有的输出设备-R
告诉它以相反的方向工作-s
告诉它要复制多少输入(我使用后缀s
来指定扇区数)-o
告诉它在写入之前在输出设备中向前查找(再次使用后缀在扇区中指定s
)/dev/sdj11
是要读取的块设备/dev/sdj11_copy
是要写入的块设备
我创建了/dev/sdj11_copy
来mknod
匹配 的参数/dev/sdj11
。
我只做了一些非常快速的测试,但这似乎可以正常复制原始设备。它不适用于文件(我无法欺骗它超出文件相同的范围)
这并没有回答我最初的问题,即如何实现这一目标,dd
但我认为,在阅读了其他答案后,答案是dd
不能做到这一点。