2011年5月17日更新
ファイルサーバを読む
ブロック
以下でいうブロックとは論理ブロックで、 特定の場合を除いて、ディスクの物理ブロックとは異なります。 キャッシュもWORMも同じ大きさで8KB。
チャネル
ファイルサーバに9pで接続しているコネクション。 ファイルサーバのコンソールも含みます。
バッファ
ディスクブロックにアクセスする際、 ファイルサーバは必ずバッファを通して読み書きします。 getbufはデバイスとアドレスを取り、 それのバッファを用意してロックします。 putbufはその逆です。
Cache-WORMデバイスの場合は、 キャッシュディスクにWORMのデータを読み込んだり、 dumpの時にキャッシュのデータをWORMへ反映したりします。 バッファの詳細は下のほうで。
デバイス
- Cache-WORM(cw)
- キャッシュとWORMを両方持つデバイス
- dump(ro)
- 9fs dumpしたときに見えるデバイス(2011/0411とか)
- リードオンリー
- Cache(c)
- キャッシュ
- WORM(w)
- WORM
- だいたいCache-WORMの一部分
別の記事でfsバックアップメモも参考に。
アドレス
ファイルサーバのなかで管理しているアドレスは、 デバイスごとに違うものとして扱われます。 なので、getbuf(cw, 2)をマップした結果、 そのキャッシュブロックアドレスが10としても、 それとgetbuf(c, 10)は異なるアドレスとして扱われます。
アドレス変換
Cache-WORMデバイスを扱う場合は、 WORMアドレスからキャッシュのデータブロックへマップするため、 アドレス変換が発生します。 このとき、getbufに与えるアドレスはWORMアドレスです。
詳細はファイルサーバのディスクレイアウトに。
データ構造
ブロックアドレス関連の定数
- CACHE_ADDR
- Cacheディスク構成情報などが配置されるブロックアドレス
- 2
- SUPER_ADDR
- WORMにある最初のスーパーブロックアドレス
- 2
- ROOT_ADDR
- WORMのルートブロックアドレス
- RBUFSIZE
- 論理ブロックの大きさ
- 具体的には8KB(8192)
- BUFSIZE
- RBUFSIZEからタグのぶんを除いた、データを持てる大きさ
- BUFSIZE+sizeof(Tag) == RBUFSIZE
- BKPERBLK
- 1つのブロックに含まれるBucket数
- 10
- CEPERBK
- Bucketに含まれるCentry数
- だいたい50
- ADDFREE
- grow時に増えるWORMブロック数
- 100
- DIRPERBUF
- 1論理ブロックに保存できるDentry数
ブロックの種類
- Tfile
- ファイル
- Tdir
- ディレクトリ
- Tsuper
- WORMのスーパーブロック
- Tind1
- 1段目の間接参照ブロック
- 2段目、3段目はTind1+1, Tind1+2となる
- Tfree
- フリーリスト
- Tbuck
- キャッシュディスクのマップブロック
ブロックの状態
- Cread
- WORMに書き込み後、変更がまだないブロック
- Cwrite
- WORMに書き込み後、変更のあったブロック
- Cdirty
- 新しく確保されたブロックで、WORMにもない
- Cdump
- dumpキューに入っているブロック
- Cdump1
- dumpエラーのブロック?
- Cnone
- 未確保
Centry
WORMアドレスとキャッシュディスクのブロックを関連付けするもの。 詳細は下のほうに。
- waddr
- WORMブロックのアドレス
- state
- ブロックの状態
Bucket
Centryの配列を持つ。 これが1ブロックにBKPERBLK個数格納される。 このあたりはファイルサーバのディスクレイアウトを参照。
Cache
キャッシュの状態と、一部、WORMの状態を持つ。 常にキャッシュディスクのCACHE_ADDRに置かれる。
- msize
- Bucket数
- msizeとなっているがブロック数ではない
- maddr
- マップ領域(Bucket)の開始アドレス
- 常にCACHE_ADDR+1 = 3
- caddr
- データ領域の先頭ブロックアドレス
- csize
- キャッシュディスクの使用可能なfs論理ブロック数
- マップ領域分(msize/BKPERBLK)は含まない
- sbaddr
- cwraddr
- roraddr
- next
- fsize
- wsize
- これらはSuperbの同名変数と同じ値を維持している
Superb
WORMに保存されるスーパーブロック。
- last
- そのSuperbからみて前回のスーパーブロックアドレス
- 最初は2
- cwraddr
- ルートブロックのアドレス
- 最初は3
- roraddr
- dumpデバイスのルートアドレス
- 最初は4
- next
- 次のスーパーブロックアドレス
- 最初は5
- fstart
- 2
- fsize
- 使用中WORMブロック数
- 最初は6
- fsgrow()のときにADDFREEだけまとめて増える
- dumpのときにもCwriteブロックの場合に1増える
- wsize
- WORMブロック数
- fbuf
- フリーブロック
Fbuf
フリーブロックアドレスの配列。 新しいブロックが割り当てられる(Cdirty)時に後ろから使われる。
これがFEPERBUFを超えると、超えたFbufをブロックへ書き、 新しいフリーブロックの先頭に書きこんだアドレスを設定する。 このため、間接参照みたいな扱いになる。 このときのtagはTfree。
- nfree
- ブロックの個数
- free
- フリーブロックのアドレス配列
- 0のみ特別
Cw
キャッシュWORM
- fsize
- dumpのときに共有変数っぽく使う
- 意味はCacheなどのそれと同じ
- daddr
- dump対象となったブロックアドレスっぽい
Wren
物理的な磁気ディスク。
- nblock
- SCSI論理ブロック数
- block
- SCSIブロック長
- 単位はバイト
- mult
- RBUFSIZEを確保するのに必要なSCSI論理ブロック数
- max
- 最大ブロック数
- ブロックのサイズはRBUFSIZE
Iobuf
各種ブロック操作を行うときに使うバッファ。 getbufにより空いているバッファがロックされ使用中になり、 putbufによってロック解除され未使用状態に戻る。
- addr
- WORMアドレス
- dev
- デバイス
- flags
- BmodとかBreadとかのフラグ
- これはputbufで処理(キャッシュに書き込むなど)
- iobuf
- 各種操作を行うためのバッファ
- Iobufが使われていない場合はnilになっている
- read/writeなどで使う
- xiobuf
- 実際のバッファ
- 事前にメモリを確保していて、iobufmapによりこれをiobufへ設定
Dentry
ディレクトリエントリ。 直接ブロック6個、間接ブロック4個などの情報が保存されている。 fs64の場合、1つのブロックに47個入るらしい。
ディレクトリがブロックをまたいで分断されることはない。 なので(mode&DDIR)なら、 dblock[0]もdblock[1]もgetdir(block, i)が使える。
- mode
- パーミッション
- dblock
- 直接ブロックのアドレス配列
- iblocks
- 間接ブロックのアドレス配列
- slot
- バッファ中のインデックス
- 同じ親ディレクトリでも、ブロックが異なればまた0からはじまる
File
オープンファイル。 詳細はfsオープンファイルの管理を参照。
- cp
- ファイルを扱っているチャネル
- 未使用ならnil
- wpath
- 親ディレクトリを指すリスト
- addr
- ファイルのDentryが保存されているブロックアドレス
- slot
- ファイルのDentry中で、何番目のDentryかを指すオフセット
Wpath
オープンファイルについて、その親ディレクトリを指す。
- up
- さらに上位ディレクトリ
- Wpathが/の場合はnil
- refs
- 参照カウント
- addr
- slot
- Fileのそれと同じ
グローバル変数
- flist
- オープンファイルのリスト
- sdevs
- たぶんディスクコントローラ
各種操作
バッファ
ファイルサーバは、ディスクのブロックにアクセスする場合、 getbufとputbufを使って、必ずバッファを通して扱います。
getbuf(dev, addr)
単純にするため引数を一部省略していますが、getbufは、 デバイスとWORMアドレスを使ってバッファを使用可能にします。 必要ならWORMからバッファへ読み込んだりもします。 次のはあくまで疑似コード。実際は全然違うけど雰囲気だけ。
mem: array of list of ref Iobuf
getbuf(dev: ref Device, addr: Off): ref Iobuf
{
list = mem[hash(addr)].find(a => !a.used);
c = list.find(a => a.dev == dev && a.addr == addr);
if(c == nil)
c = list.last;
lock(c);
c.dev = dev;
c.addr = addr;
必要なデータの準備(c);
return c;
}
必要なデータの準備のところで、 実際にブロックへ読み書きをします。 Cache-WORMデバイスの場合は以下のように呼び出しします。
getbuf(cw->dev, up->addr, Bread|Bmod)
devread(cw->dev, up->addr, buf) # 0なら正常終了
cwread(cw->dev, up->addr, buf)
cwio(cw->dev, up->addr, buf, Oread)
ここから下はふつうにSCSIコマンド。
ream
Cache, Superb, 2つのルート(cwとro)を設定します。 これもfsバックアップメモのほうに。
grow
必要に応じてWORMの容量を増加させます。 増加したブロックは、フリーブロックリストに移ります。
cwgrow: fn(dev: ref Device, sb: ref Superb, uid: int): int
{
h: ref Cache;
h = getbuf(CDEV(dev), CACHE_ADDR, ...);
h.fsize += ADDFREE;
if(h.fsize >= h.wsize)
h.fsize = h.wsize;
sb->fsize = h.fsize;
for(waddr in 増えたブロックについて){
cwio(dev, waddr, 0, Ogrow);
addfree(dev, waddr, sb);
}
}
remove
ファイルを削除したとき、 そのファイルが使用しているブロックをみて、 それがCdirtyならフリーリストへ戻します。 Cwriteなどの場合は再利用すると整合性が取れなくなるので、 フリーリストへは戻しません。
recover
いちばん新しいdumpの状態に戻します。 実際の動きはfsバックアップメモを参照。
dump(queue)
ファイルサーバは朝5:00に、 その時点のバックアップをWORMへ書き込むのですが、 それは変更のあったブロックをdumpキューへ入れる部分と、 キューのブロックを実際に書き込む部分に分かれています。 ここではキューに入れるところについて。
まず、ファイルサーバは、ファイルの操作ができないように 全体をロックします。 変更のあったブロックは必ずキャッシュに残っているので、 キャッシュディスクから変更のあったブロックをみて、 その状態をCdumpに書き換えます。 ここで、実際はデータ領域ではなくマップ領域をみて、 そのブロックが変更されていなければ飛ばします。
変更のあったブロックで、その状態がCwriteの場合、 新しいブロックアドレスを割り当てます。 これはフリーブロックを使いません。 WORMディスクの容量(fsize)を増やしながら割り当てます。
na = cw->fsize++
cwio(cw->dev, na, 0, Ogrow)
cwio(cw->dev, na, p->iobuf, Owrite)
cwio(cw->dev, na, 0, Odump)
cwio(cw->dev, addr, 0, Orele)
Cdirtyの場合はまだ未使用なのでそのままです。
cwio(cw->dev, addr, 0, Odump)
この再割り当てはcwrecur関数が処理しています。 これは深さ優先で探索するので、 深い場所のほうが小さいアドレスになるみたい。 で、どこかに変更があった場合はrootがwriteになっているので、 最後の戻り値が新しいルートの値(最大アドレス)となっている。 ちなみに、cwrecurの戻り値は、変更がなければ0。
アドレスの再割り当てが終わったら、 rewalk関数により、オープンファイルが 持っているアドレスを新しいアドレスに更新して、 サーバの処理を再開します。
OreleとOfreeの違いは、
- rele
- writeならnoneに
- free
- writeまたはreadならnone
dump(copy)
キューに入れられたブロックについて、 その内容をWORMに書き込むプロセスをwcpといいます。 これは朝5:00に限らず動いていて、 ブロックの状態がCdumpのものを 小さいアドレスから順に探して書き込みます。 その処理はdumpblock関数あたり。
全部処理が終われば、cw->nodump = 1として停止。 次にdumpキューへ入れられればまたnodump = 0となり プロセスが活性化します。
メモ
SCSIドライバのロード
pc/scsi.cに、名前と関数(reset)を登録するテーブルがある。 これをpc/scsi.c:scsiinitから調べて、 一致すれば関数の戻り値をコントローラ構造体のioに入れる。 関数(reset)は、scsiの入出力を処理する関数(Scsiio)を 返すようになっているので、 デバイスドライバ依存の処理は全部これを通して扱うみたい。 ちなみに同名の関数がいくつかあるけど、#ifdef FSのものが有効。