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() していることに注意。