每次我将文件写入空的原始块设备时,例如
# dd if=image.iso of=/dev/sdb status=progress
我从未使用过任何类型的sync
(即sync
;;;;;)。conv=fsync
conv=fdatasync
oflag=sync
oflag=dsync
我注意到这dd
并没有曾经退出直到所有写入完成。
我总是使用 Conky 的 I/O 工具和grep Dirty /proc/meminfo
.此外,设备的校验和始终与写入该设备的文件的校验和相匹配。所以我始终 100% 确定整个文件已写入设备。
我已将文件写入 ext4 卷进行比较。例如使用:
$ dd if=/dev/urandom of=~/file bs=1M count=50 iflag=fullblock
写入 ext4 卷时,dd
退出后总会有大约 20 秒的延迟,然后数据才会真正写入磁盘。
许多人主张在写入块设备时sync
在命令之后使用该命令,或者在命令中dd
包含几个选项之一。例如sync
dd
这里和这里。然而,我还不知道有人真正证明这是必要的。
其中一条评论是这一页是:
sync
在这里毫无意义[即直接写入/dev/sdX
]。它仅影响文件系统操作。
有五个人对此评论表示赞同,这与我的经验一致。
那么在写入块设备时,是否有什么情况dd
会在所有写入完全完成之前退出呢?这真的发生在任何人身上吗?
其他书写选项(例如cp
和)怎么样cat
?它们可以在写入块设备完成之前退出吗?
答案1
是时候回答这个问题了。
很长一段时间以来,我一直认为只缓存对文件系统的写入,而不缓存块设备,结果发现我错了:对块设备的写入会被缓存。
严格来说我没有官方的来源,并将在我得到答案时更新我的答案,但您可以在这里找到一些信息:linux - 块设备缓存与文件系统。抱歉,我刚刚注意到您也看到了这个问题!
事实上,我在将映像添加到 USB 记忆棒时遇到了问题,完成后将其弹出,然后发现它已损坏。它不应该有这样的行为,因为似乎完全同步是在设备上的最后完成的close()
,因此我想要么我的设备上出现数据损坏,要么我错过了仍然打开块设备的进程,但无论如何我不会再冒险了。
所以,是的,在写入块设备时添加同步选项dd
似乎并不是一个都市传说。我认为conv=fdatasync
应该足够并且性能最佳(毕竟没有添加文件元数据,并且只需要在过程结束时同步),但我们中间的极端主义者可能更喜欢oflag=sync
(每次写入后完整数据+元数据同步)。检查man 2 open
并man 2 fdatasync
获取更多见解。
根据我读到的内容(不幸的是,目前还没有正式的消息),如果其他进程仍然打开块设备,cp
则cat
块设备确实可以在同步之前退出,除非在设备启动时使用某些 SYNC 标志明确请求open()
ed 或使用同步系统调用,例如fdatasync()
,fsync()
或者更夸张的是sync()
,这些命令都没有......
答案2
所以我一直在测试类似的东西,告诉我们的是 dd 进程的堆栈跟踪。
我直接对块设备进行了 dd 操作(注意没有 oflag=direct ,它绕过了页面缓存),包括 status=progress ,我得到的是:状态会显示速度非常快 187MB/s(作为写入的时间量)页缓存很小),但是然后 dd 在块设备上调用 blkdev_close ,然后等待,完成后,您将获得基于写入总时间(包括 close 调用上的刷新)的速度,因此速度要慢得多。
echo;echo;echo;date; dd if=/dev/urandom bs=1M of=/dev/mockdevice status=progress; date
Thu 08 Oct 2020 10:47:36 AM EDT
3172990976 bytes (3.2 GB, 3.0 GiB) copied, 17 s, 187 MB/s
dd: error writing '/dev/mockdevice': No space left on device
3073+0 records in
3072+0 records out
3221225472 bytes (3.2 GB, 3.0 GiB) copied, 1681.94 s, 1.9 MB/s
Thu 08 Oct 2020 11:15:38 AM EDT
这是 dd 完成后刷新时的堆栈跟踪(在我的例子中,它在磁盘已满时出错)
cat /proc/1130961/stack
[<0>] __lock_page+0x128/0x230
[<0>] write_cache_pages+0x354/0x4b0
[<0>] generic_writepages+0x57/0x90
[<0>] blkdev_writepages+0xe/0x10
[<0>] do_writepages+0x43/0xd0
[<0>] __filemap_fdatawrite_range+0xd5/0x110
[<0>] filemap_write_and_wait+0x44/0xa0
[<0>] __blkdev_put+0x72/0x1e0
[<0>] blkdev_put+0x4e/0xe0
[<0>] blkdev_close+0x26/0x30
[<0>] __fput+0xcc/0x260
[<0>] ____fput+0xe/0x10
[<0>] task_work_run+0x8f/0xb0
[<0>] exit_to_usermode_loop+0x131/0x160
[<0>] do_syscall_64+0x163/0x190
[<0>] entry_SYSCALL_64_after_hwframe+0x44/0xa9
还值得注意的是,当您使用 dd 填充页面缓存时,页面缓存会一次刷新一页到块设备。文件系统将发送带有大量页面的 BIOS 来刷新一个 Bio,但是上面堆栈跟踪中的页面缓存刷新器似乎只为每个 Bio 推出一页。
因此,据我的经验,您不必调用sync,因为 blkdev_close 实际上在返回并让 dd 完成之前将所有脏缓存页面刷新到块设备。