Linuxのローカルファイルシステムの話です。かなりハマっていたので記事として残しておきます。

既存ファイルがある状態でO_TRUNCを指定してopen(2)を呼び出し、適当に書き込んだあとclose(2)すると、非同期ながらWritebackされ物理IOが発生します。close時はfsyncせず、書き込まれたデータはDirty状態でページキャッシュに保存されるだけのはずなのだが(参考:Linux の close は fsync 相当を調べる – naoyaのはてなダイアリー)。。。何を言っているのかわからねーと思うので、状況を再現する手順は以下のとおり。

  • ファイルを適当に作っておく。

$ echo test > test.dat

  • 以下のようなソースファイルを作成し、コンパイルしておく
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
  int fd;
  char *p = malloc(10*1024*1024);

  fd = open("test.dat", O_TRUNC | O_CREAT | O_WRONLY, 0666 );
  write(fd, p, 10*1024*1024);
  close(fd);

  return 0;
}
  • 実行する前に、/proc/meminfo の Dirty・Writeback の値を確認する。
  • 実行する(ディスクランプ・iostatでディスクへの書き込みを確認する)
  • 実行後、/proc/meminfo の Dirty・Writeback の値を10秒間程度確認し、実行前に比べDirtyの値が10M増えたままであればsyncされていないが、Dirtyの値が変わらない、もしくは、Dirtyの値が増えた後減って実行前の状態に戻った(そのときwritebackの値が0より大きい値)、ようであれば writeback されていることになる。

※ vm_dirty*** 系のパラメタはこのタイミングでwritebackされないように適当に大きくしておく。

色々試してみたところファイルシステム依存で、ext3(data=writeback), xfs についてはclose(2)時にディスクへの書き込みが行なわれた。ext3(data=ordered), ext2, reiserfsについてはそのようにはならなかった。また、ext3(data=writeback), xfs でも既存ファイルが無い状態での O_TRUNC ではWritebackされず、Dirtyのまま残る。

ファイルシステム毎に試してみたところ以下のようになった。(たぶん合っていると思いますが、間違っていたら指摘ください)

ファイルシステム 既存ファイル無しでO_TRUNC 既存ファイル有りでO_TRUNC
ext2 close(2)のタイミングでデータはwritebackされず、Dirtyのままである 同左
ext3
(data=ordered)
close(2)のタイミングでデータはwritebackされず、Dirtyのままであり、メタデータ・データはcommit間隔にて同期される。 close(2)のタイミングでデータはwritebackされず、Dirtyのままであり、メタデータ・データはcommit間隔にて同期される。(しかし、同期された後もDirtyからは消えない?)
ext3
(data=writeback)
メタデータはcommit間隔にて同期される。close(2)のタイミングでデータはwritebackされず、Dirtyのままとなる。 close(2)時にwritebackされる(close(2)はsyncの復帰を待たず復帰)
reiserfs close(2)のタイミングでデータはwritebackされず、Dirtyのままである 同左
xfs close(2)のタイミングでデータはwritebackされず、Dirtyのままとなる。 close(2)時にwritebackされる(close(2)はsyncの復帰を待たず復帰)

今まで、ファイルへの書き込みはLinuxのページキャッシュ機構により、非同期でwritebackされると思っていたがこういうタイミングでもwritebackされるとは思っていなかった。お仕事でLinuxを使う場合は、close(2)のターンアラウンドタイムとディスクアクセスが性能見積りと異なる場合があるので要注意かなぁ。(こういう仕組みになっているのは、どうやらジャーナルとデータとの関係のようだが詳細が分かったらまた追記します)どうやらtruncateしてファイルサイズが0でファイルの実体がsyncされていない状態でクラッシュすると、ファイルが失われるため、それを防止するためにtruncate時は早めにsyncするようです。

参考までにSDカードにファイルシステムを作ってみて、上記プログラムをstrace上で動かしてみたときのシステムコールの時間を残しておきます。

既存ファイル有りでO_TRUNC:(単位はms)

ext2 ext3(data=writeback) ext3(data=ordered) reiserfs xfs
open 0.065 0.0925 0.086 0.134 0.184
write 53.775 90.2 227.090 65.586 37.911
close 0.027 17.874 0.026 0.047 8.159

(太字にした)ext3, xfs のclose時にwritebackの処理のため(?)に時間がかかるようです。また既存ファイルが無い状態での同じプログラムを動かした結果は以下のようになり、close時のwritebackが行なわれませんでした。

既存ファイル無しでO_TRUNC:(単位はms)

ext2 ext3(data=writeback) ext3(data=ordered) reiserfs xfs
open 0.0685 0.075 0.074 0.1 0.134
write 55.501 93.967 134.721 67.165 37.093
close 0.0255 0.028 0.024 0.046 0.0815

2011/1/4 追記:
ext3 のマウントモードがwritebackである旨を追加し、ordered の場合の動作追記。

コメントを残す

メールアドレスが公開されることはありません。