disk syncの挙動を細かく制御する
mmap I/Oアプリケーションにおけるdisc sync
ファイルを読み書きするようなアプリケーションにおいて、キャッシュの管理をOS任せにしても良いのならば、ファイル全体をmmapしてしまうと非常に楽にI/O処理を書くことができる。
しかし、mmapしてデータを書き込むだけで、即座にディスクにデータが書き込まれるわけではない。
明示的にmsync(MS_SYNC)を発行して同期を取る必要がある。
ここまでは一般的な話。
さて、0x1000-0x2000, 0x8000-0x10000といった複数の領域を同期する為にはどうするか。ナイーブな実装としては、msync(MS_SYNC)を2回に分けて発行する方法がある。
msync(0x1000, 4096, MS_SYNC); msync(0x8000, 8192, MS_SYNC);
しかし、これでは最初の領域を確実に書き込んだ後、改めて次の領域を書き込む処理を発行するため、効率がわるい。
これら2つをいっぺんに発行するにはどうするか。MS_ASYNCでdirtyだと明示しておいて、あとでまとめてfsyncで書き込んでやれば良い。というかMS_ASYNCフラグの存在意義が長らく謎だったんだけど、こういう具合につかうのね。
msync(0x1000, 4096, MS_ASYNC); msync(0x8000, 8192, MS_ASYNC); fsync(fd);
さらに補足すると、Linux 2.6.17以降ではmsync(MS_ASYNC)は何もしないため、これらのmsyncの発行自体不要で、fsyncだけすれば良いらしい。
cf. http://lxr.free-electrons.com/source/mm/msync.c
fsync(fd);
MS_ASYNCなんてやっぱり要らなかったんや...!
ファイルのメタデータの更新を抑えて高速化する
DBみたいにアホみたいにfsyncを発行するアプリケーションを書いていると、fsyncがファイルのメタデータを更新するオーバーヘッドが気になり始める。
fsync(2)が行うことは実は2つある。1つはwrite/msync等が発行されながら、実際に書き込まれていなかったデータをディスクにフラッシュするという動作で、おそらく皆さん意識することだろうと思う。もう1つはファイルのメタデータの更新動作で、ファイルの更新時刻(mtime)やファイルサイズのエントリを更新する。
このメタデータの更新動作は、ファイルサイズの変更を伴わない、データの上書きを行う場合では不要になる。
その場合、fsync(2)の代わりにfdatasync(2)を行うことで、メタデータの更新を避けることができる。
fdatasyncはtime系以外の更新がない場合、メタデータの更新を省略する。
cf. http://linux.die.net/man/2/fdatasync
同じことは、範囲指定版fdatasync(2)であるsync_file_range(2)を使っても可能。ただこちらはメタデータの更新を絶対に行わない?(指摘より)
http://linux.die.net/man/2/sync_file_range
また、上記のようにmmap I/Oを行っている場合でも有効で、msync(MS_SYNC)はメタデータの更新を伴うが、msync(MS_ASYNC); fdatasync(fd)と呼び出すことでメタデータの更新を省略することができる。
ただし、以上の話はデータの上書きにのみ適用できることに注意。
新規データの書き出しには、データを書き込むブロックの確保というメタデータの操作が発生するため、fsync(2)を使わないと安全ではない。
あと、linux以外の挙動に関してはよく知らない。