UNIX 6th code reading - バッファの状態遷移

はじめに

前回のエントリでバッファに関する説明を書きました。

ソースを追っているだけではバッファの状態遷移の仕方がわかりづらかったので絵を描いてみました。

各フラグの説明

主要なものだけ簡単に。詳細はThe UNIX I/O Systemを読んでください。

  • B_READ
    • 読み込みを行うとき、rkstrategy( )を呼ぶ前に立てる。これを立てないと書き込みになる
  • B_DONE
    • このバッファは最新の情報を持っている。デバイスと同期が取れている(読み込み・書き込み)、もしくは、デバイスより新しいデータを持っている(書き込み)
  • B_BUSY
    • このバッファは使用中。av-listに存在しない
  • B_WANTED
    • このバッファは使用中で、解放されるのを誰かが待っている状態
  • B_ASYNC
    • 先読み、非同期書き込みを行う。デバイスの動作完了を待たない。動作完了時に呼ばれるiodone( )内でB_BUSYがリセットされる
  • B_DELWRI
    • 遅延書き込み。ライトバック。すぐにデバイスに書き込まず、getblk( )でバッファが再割り当てされるときや、bflush( )が呼ばれたときに書き込まれる

getblk( )

欲しいバッファがb-list内でB_BUSYだったときはB_WANTEDフラグを立てて寝ます。そのバッファを使っている別のプロセス(B_ASYNC読み書きを考えると、同じプロセスの場合もありうる?)がbrelse( )でバッファを解放すると起きます。

特徴的なのはav-listからバッファを取得しようとしたときに、それがB_DELWRI(遅延書き込み)バッファならば非同期(B_ASYNC)でデバイスへの書き込みを行うことです。

av-listからバッファを取得した場合、B_BUSY(とB_RELOC)だけフラグが立つことに注意してください。bp->b_flags =| ではなくbp->b_flags = です。4966行参照。

bread( )

B_DONEフラグが立っていた場合は、デバイスへのアクセスを行いません。ディスクキャッシュの役割を果たしています。

breada( )

breada( )は実装がごちゃごちゃしているように思えます。

大きな流れは、通常の読み出し処理→先読み処理(B_ASYNC)→通常読み出しの後処理、てな感じです。

先読み処理は、デバイスからの読み出し処理完了を待ちません。デバイスからの読み出し処理が完了するとiodone( )が呼ばれ、そこでbrelse( )が呼び出されてB_BUSYフラグが勝手にリセットされます。

bwrite( ), bawrite( )

bawrite( )はB_ASYNCをセットしてからbwrite( )を呼び出します。

B_ASYNC書き込みの場合はデバイスへの書き込み終了を待ちません。デバイスへの書き込み処理が終わった後iodone( )が呼び出され、そこでbrelse( )が呼び出されB_BUSYフラグが勝手にリセットされます。

通常書き込みの場合はデバイスへの書き込み終了を待ちます。デバイスへの書き込み処理が終わった後iodone( )からwakeup( )で起こされ、自分でbrelse( )を呼び出してB_BUSYフラグをリセットします。

bwrite( ), bawrite( )を実行する前に、getblk( )などでバッファを取得しておく必要があります。

bdwrite( )

bdwrite( )はB_DELWRI(遅延書き込み)フラグとB_DONEフラグを立ててからbrelse( )を呼び出しB_BUSYフラグをリセットしav-listに戻すだけです。

実際に書き込み処理が行われるのは、getblk( )で書いたように、av-listからそのバッファを取得するときにB_ASYNCで書き込まれます。また、update( )などからbflush( )が呼ばれ、B_DELWRIのバッファがまとめてB_ASYNCで書き込まれます。

終わりに

今回描いた状態遷移図にプラスしてbinit( )による初期状態も理解しておくと、バッファの全体像が見えてくるでしょう。

状態遷移図は改良の余地があると思っています。なのでこの図だけで理解しようとせず、ソースを見ながら補足的に使うことをお勧めします。

エラー周りは省略しました。しかし、B_ASYNC動作に関してどこでエラーを拾うのかなどはきちんと考える必要がありそうです。私もまだ理解できていません。

次回はようやくファイルシステムに入っていくつもりです。