write(fd with O_SYNC) 是否仅刷新该 fd 的数据,而不是由同一文件的其他 fd 引起的所有缓存?

write(fd with O_SYNC) 是否仅刷新该 fd 的数据,而不是由同一文件的其他 fd 引起的所有缓存?

我正在使用 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=f​​sync之间的区别并不明显,所以我深入研究了dd的来源,结果发现

  • oflag=sync 将导致打开带有 O_SYNC 标志的输出文件,每个写入系统调用将自动导致 fsync(fd)。
  • conv=f​​sync 会在每次写入时导致额外的 fsync 系统调用。
  • oflag=direct 要求块大小乘以 512 等,对于我的情况,它只是 1 个字节,dd 只需关闭该标志,将其更改为 conv=f​​sync。

一切似乎都很好,但我不确定一件事:

如果输出文件/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=syncsync命令更安全。

编辑:

我已经确认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

相关内容