我正在使用 dd 命令更改块设备(不是分区块设备)的单个字节,例如/dev/nvme0n1
,在特定位置(不由普通文件管理)。
dd of=${DEV:?DEV} seek=${POS:?POS} bs=1 count=1 oflag=seek_bytes conv=notrunc status=none
我遇到了命令问题sync
,它在某些机器上挂起或需要很长时间才能完成。
看来该sync
命令涉及所有文件的缓存,这显然会很慢,甚至会由于某些不一致的内核管理而挂起。尤其是主机上运行着几个大的VM,同步会很慢,有时要30分钟。
然后我开始认为我不应该sync
直接调用命令,我应该告诉 dd 仅通过 同步它所涉及的部分,如下所示oflag=sync
:
dd of=${DEV:?DEV} seek=${POS:?POS} bs=1 count=1 oflag=sync,seek_bytes conv=notrunc status=none
由于oflag=direct、oflag=sync、conv=fsync之间的区别并不明显,所以我深入研究了dd的来源,结果发现
- oflag=sync 将导致打开带有 O_SYNC 标志的输出文件,每个写入系统调用将自动导致 fsync(fd)。
- conv=fsync 会在每次写入时导致额外的 fsync 系统调用。
- oflag=direct 要求块大小乘以 512 等,对于我的情况,它只是 1 个字节,dd 只需关闭该标志,将其更改为 conv=fsync。
一切似乎都很好,但我不确定一件事:
如果输出文件/dev/nvme0n1
有很多 Linux 缓存的文件,那么我的 dd 命令会触发它最终同步所有文件吗? (实际上我只想将 1 个字节同步到设备,而不是其他内容。)
我检查了内核源代码,猜测 write(带有 O_SYNC 标志的 fd) 最终调用 [fs/sync.c#L180)(https://github.com/torvalds/linux/blob/16a8829130ca22666ac6236178a6233208d425c3/fs/sync.c#L180 )(至少这是 fsync 系统调用最终调用的)
int vfs_fsync_range(struct file *file, loff_t start, loff_t end, int datasync)
{
struct inode *inode = file->f_mapping->host;
if (!file->f_op->fsync)
return -EINVAL;
if (!datasync && (inode->i_state & I_DIRTY_TIME))
mark_inode_dirty_sync(inode);
return file->f_op->fsync(file, start, end, datasync);
}
但后来我被困在
file->f_op->fsync(file, start, end, datasync)
我不确定文件系统驱动程序如何处理fsync,是否涉及其他fd引起的所有缓存,这并不明显。
我将继续检查内核源代码并稍后追加编辑。
编辑:我几乎可以肯定这vfs_fsync_range
是系统调用最终调用的write
。
堆栈是这样的
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
size_t, count)
{
return ksys_write(fd, buf, count);
}
ssize_t ksys_write(unsigned int fd, const char __user *buf, size_t count)
{
...
ret = vfs_write(f.file, buf, count, ppos);
}
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
...
if (file->f_op->write)
ret = file->f_op->write(file, buf, count, pos);
else if (file->f_op->write_iter)
ret = new_sync_write(file, buf, count, pos);
...
}
static ssize_t blkdev_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
...
ret = __generic_file_write_iter(iocb, from);
if (ret > 0)
ret = generic_write_sync(iocb, ret);
...
}
static inline ssize_t generic_write_sync(struct kiocb *iocb, ssize_t count)
{
if (iocb_is_dsync(iocb)) {
int ret = vfs_fsync_range(iocb->ki_filp,
iocb->ki_pos - count, iocb->ki_pos - 1,
(iocb->ki_flags & IOCB_SYNC) ? 0 : 1);
if (ret)
return ret;
}
return count;
}
待续...
static int blkdev_fsync(struct file *filp, loff_t start, loff_t end,
int datasync)
{
struct block_device *bdev = filp->private_data;
int error;
error = file_write_and_wait_range(filp, start, end);
if (error)
return error;
/*
* There is no need to serialise calls to blkdev_issue_flush with
* i_mutex and doing so causes performance issues with concurrent
* O_SYNC writers to a block device.
*/
error = blkdev_issue_flush(bdev);
if (error == -EOPNOTSUPP)
error = 0;
return error;
}
应该是上面blkdev_fsync
做同步工作。从这个函数来看,就变得很难分析了。希望一些内核开发人员可以帮助我。
上述函数进一步调用函数 毫米/文件映射.c
和块/blk-flush.c, 希望这可以帮助。
我会做一个测试,但是测试不能让我有信心......这就是为什么我来这里问这个问题。
经过测试,但由于sync
命令本身也很快完成,我不能说 ifdd oflag=sync
比sync
命令更安全。
编辑:
我已经确认dd oflag=sync
比指挥更安全、更快捷sync
,我相信这个问题的答案是肯定的。
write(fd with O_SYNC) 是否仅刷新该 fd 的数据,而不是由同一文件的其他 fd 引起的所有缓存?
是的。
测试是这样的:
- 用随机数据重复创建大文件
for i in {1..10}; do echo $i; dd if=/dev/random of=tmp$i.dd count=$((10*1024*1024*1024/512)); done
- 换句话说,跑去
sync
确认会很慢,就像挂在那里一样。中断同步命令。 - 创建一个测试文件,获取其物理LBA。
echo test > z
DEV=$(df . | grep /dev |awk '{print $1}')
BIG_LBA=$(sudo debugfs -R "stat $PWD/z" $DEV | grep -F '(0)' | awk -F: '{print $2}')
- 换句话说,运行 dd 命令,确认它非常快。
dd of=${DEV:?DEV} seek=$((BIG_LBA*8*512)) bs=1 count=1 oflag=sync,seek_bytes conv=notrunc status=none <<<"x"
但我还是希望有人能指出源代码中哪里可以确认答案。
答案1
我不确定您是否正在挂载/dev/nvme0n1
以及直接写入设备上的某个块,但在任何情况下,您都可以使用以下命令来跟踪设备上执行的 i/o 操作:密件抄送,BPF 编译器集合。它有一个工具,biosnoop
可以显示系统中磁盘的命令、进程 ID、块号、读取或写入长度以及每个 I/O 的延迟。在Fedora中,有一个bcc-tools
包,想必在其他发行版中也有类似的包。
$ sudo /usr/share/bcc/tools/biosnoop
TIME(s) COMM PID DISK T SECTOR BYTES LAT(ms)
8.241172 dd 12525 sdc R 4000 4096 0.97
8.242739 dd 12525 sdc W 4000 4096 1.17