Softupdate のひみつ
山本和彦
2000年2月24日
最近の Unix では、UFS (スーパープロックとデータブロックがあるいわゆる Unix のファイルシステム)に対して、sync、async、softupdate という書き込み方があります。特徴を表にすると以下のようになります。
速度 安全性 sync 遅い 安全 async 速い 危険 softupdate 速い 安全
まず速度。メモリへの書き込みは速いが、ディスクへの書き込みは遅いというのが大前提です。速度を向上するには、ディスクの内容をメモリにキャッシュし、メモリを書き換えることで、書き込みの速度を向上させます。これを定期的にディスクに書き出します(delayed write)。昔は、update デーモンくんが 30 秒に一回書き出していました。
次に安全性ですが、
- ファイルシステムの構造を維持できること
だとします。(ファイルの内容が壊れないことじゃないよ。)
具体的には、
- ファイル名がおかしな inode (スーパーブロックにある情報)を参照しないこと
- あるディスクブロックを指す inode を高々 1 個にすること
です。
これを実現するには、3 つの規則を守らないといけません。
- ファイルを生成するときは、inode を初期化してから、ファイル名を作成し inode を参照させる
- ファイルを消去するときは、ファイル名を消去し inode への参照をなくしてから、inode を開放する
- inode を解放した後に、その inode が指していたディスクブロックをスーパーブロックのフリーリストに加える
後ろ 2 つの規則から、ファイルを削るときは、
- 名前を削除
- inode を解放
- inode が指していたディスクブロックをフリーリストに加える
という順番を守らないといけないことが分かります。
これを守らないと、どういうことが起こるか例を挙げてみます。
(例1) ファイルを作成する際に、ファイル名を作成し、ある inode を参照させ、その後 inode を初期化する手順を踏もうとしました。しかし、実際にはファイル名を作成し inode を参照させた時点で電源が落ちました。この場合、inode が何を指しているのか保証できません。ひょっとすると、消したはずのファイルを指しているかもしれません。
(例2)ファイルを削除し、ディスクブロック(a)をフリーリストに加えた時点で電源が落ちたとします。ディスクブロック(a)を指しているinode (b) は解放されていません。次にファイルを作成すると、ディスクブロック (a) が再利用され、inode (c) から指されます。ので、ディスクブロック(a)は、inode(b) と inode(c) から参照されているので、所有者が 2 人になってしまうかもしれません。
sync は安全性を確保するため、上記のルールが実施されることを保証しています。ですから、ファイルを作成したり消去したりすると、ディスクに上記の順番で書き込むために、ディスクの速度まで性能が落ちてしまいます。これが tar でファイルを展開したときに遅い理由です。(データ部分は、もちろん delated write します。)
async は、速度を追求するために、キャッシュしか書き換えません。書き込む順番も覚えていません。データが delayed write されるときに、構造の変化も書き出されます。このときの方法が、上記のルールに沿っていないので、構造が壊れる可能性があります。
softupdate は、ほぼ async と同じように、構造の変化も delayed write されます。しかし、構造の変化に関する依存関係を覚えていて、上記のルールが守られるように書き出します。
例:あるディレクトリに対し、ファイル foo を削除し、ファイル bar を生成したとします。foo と bar が参照する inode が、たまたま同じキャッシュにあるとしましょう。この場合、inode のキャッシュとディレクトリのキャッシュには、以下のような依存関係が生じます。(矢印の先端から先にやって、次に根本の方をやる。)
inode のキャッシュ ディレクトリのキャッシュ (1枚のページ) (こちらも1枚のページ) inode #1 ------------> foo inode #2 <------------ bar
めんどうなので、4 つの部分それぞれに A、B、C、D という名前を付けます。
ディスク上では メモリ上では (' は書き換えたという意味) A B A' B' C D C' D'
D' → C'、A' → B' という依存関係から、たとえば C'、D'、B'、A' の順に書き出せれば、構造は壊れません。しかし、キャッシュは全体を書き出すしかない(一部を書き出すことはできない)ので、undo と redo という方法を用います。
つまり、
- C' をどこかに保存、C へ undo、(A' C) をディスクに書き出す
- (B' D') をディスクに書き出す
- C を C' へ redo、(A' C') をディスクに書き出す
これで、ルールを守った手順でメモリとディスクの内容が同じになります。
このような方法により
- 構造の変化も delayed write するので速い
- 構造の変化をルールを守って書き出すので安全
となっています。
FreeBSD 3.x で softupdate を使うためには、/sys/ufs/ffs/README.softupdates を読んで下さい。最近 update デーモンくんは引退し、カーネルが 30 秒に1 回 sync() していることに注意。