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 = fsyncの違いは明確ではないので、ddソースを見てみましょう。

  • oflag = syncはO_SYNCフラグを使用して出力ファイルを開くようにし、各書き込みシステム呼び出しは自動的にfsync(fd)を引き起こします。
  • conv=f​​sync は、書き込みごとに追加の fsync システムコールを発生させます。
  • oflag = directでは、ブロックサイズに512などを掛ける必要があります。私の場合は1バイトに過ぎず、ddはフラグをオフにしてconv = f syncに変更しました。

すべてが大丈夫に見えますが、一つはわかりません。

出力ファイルに/dev/nvme0n1Linuxキャッシュファイルがたくさんある場合、ddコマンドは出力ファイルをトリガーして最終的にすべてのファイルを同期しますか? (実際には1バイトのみデバイスに同期し、残りは同期したくありません。)

カーネルのソースコードをチェックし、write(O_SYNCフラグを持つfd)が最終的に[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。この機能の観点からは分析が難しくなります。いくつかのカーネル開発者が私を助けることができることを願っています。

上記の関数は関数をさらに呼び出します。 mm/filemap.c

そしてブロック/blk-flush.c、お役に立てば幸いです。

テストしてみたいのですが、テストで自信はありません。それで、私がこの質問をする理由です。

テストしましたが、syncコマンド自体もすぐに完了するため、コマンドdd oflag=syncよりもifが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には、システムディスクに対する各I / Oのコマンド、プロセスID、ブロック番号、読み取りまたは書き込み長、および待ち時間を表示するためのツールがあります。 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

おすすめ記事