ロード トップ 参照元 逆検索 検索 ヘルプ

[logo] 4.3BSD-Renoのコードを読んでみよう


このページは、4.3BSD-Reno の しかも UFS 関係のコードを読んだときのメモ。

ファイルシステム の 原型とも言える V7 を理解したら、 次に読むべきは、UFS だろう。

BSD の UFS といっても いろいろあるようだ。いったいどれを読むべきなのか ...

選定の 基準は、

にした。

これを満たす最初のものが、どうも 4.3BSD-Reno らしい。

FFS はやはり、それが完成する前のファイルシステムとは 格段に違うし、 今の BSD のメインのファイルシステムでもある。
本当は、現在の FFS をちゃんと読めた方が良いのだが .... 残念ながら、私の知識では、どういう理由で 現在の形になっているのかまで 理解できそうにない。そういうわけで、最初に VFS が導入されたものを 原点と考えて、選んだ。


もくじ


さて、どこから見るつもりかというと bio とりあえず下位レベルをみてみよう。

vfs_bio.o の関数を列挙してみる。

bawrite() , bdwrite() , biodone(),  biowait(), bread(), breada()
brealloc(), brelse(), bwrite(), getblk(), geteblk(), getnewbuf(),
reassignbuf(),
mntflushbuf(), mntinvalbuf(), vflushbuf(), vinvalbuf()

V7 で、見慣れた関数名もあれば、そうでないものもある。 関数名を見ただけではわからないが... パラメータもずいぶん違う。

bawrite(bp), bdwrite(bp), bwrite(bp) このあたりは パラメータは同じ。 biodone(bp), biowait(bp) も V7 の iodone(bp), iowait(bp) に b が付いただけ に見える。

ところが、getblk/bread については、

bread(struct vnode *vp, blkno, size, struct ucred *cred, struct buf **bpp)
breada(vp, blkno, size, rabsize, cred, bpp)
struct buf *getblk(struct vnode *vp, blkno, size)
struct buf *geteblk(size)
struct buf *getnewbuf()

こんな感じになって、全然違う。breada は先読み指定付き bread で、 bread の派生と考えてよいさそうだ。geteblk は、struct buf を取ってくる だけのものらしい。さらに getnewbuf() は、geteblk の中で使っている関数で、 中身のない struct buf を取って来るもの。そして それは外部でも使っていたり する。

こういう風に 派生版を使うあるいは、もっと プリミティブを使う。 それはなぜなのか。ということは 正しく理解しなくてはならなさそうだ。

また、struct vnode をパラメータとするのはなぜなのか... そして cred とは? ここらあたりも ポイントか。

また、brealloc(), reassignbuf() といったもの これは何なのか ... これもポイントか。

すなおに、何を実装しているかを見るのも大事だが、 基本形は、bread/getblk だとして、そうでないものを敢えて使っている ところのロジックを読み それでないといけない理由を知るとか そういう読み方 も大事になってきそうである。

@ getblk(vp,blkno,size)

V7 では、(dev,blkno) の組 で、512 バイト固定の buf を割り当てていた。

まず、dev_t dev のかわりに マウントした デバイスを指す struct vnode *vp を使うようになったらしい。

512 バイト単位の I/O では I/O サイズが小さすぎるから性能が出ない。 もっと大きな buf を割り当て その単位で I/O できるようにする必要がある。 ということで、size が 追加されたようだ。

サイズは、 buf の size の上限は、MAXBSIZE = 8K らしい。 マウントするファイルシステム単位で blocksize は違うようにできる。

たとえば、既にある 512 バイトの buffer に重なるような形で、 4K の buf を getblk することはできない。そういうときは、brealloc(bp,size)を 使って、変更するらしい。

さて、実際に新しい buf を取得する手続きは、

        bp = getnewbuf();
        bfree(bp);
        bremhash(bp);
        bgetvp(vp, bp);
        bp->b_lblkno = blkno;
        bp->b_blkno = blkno;
        bp->b_error = 0;
        bp->b_resid = 0;
        binshash(bp, dp);
        brealloc(bp, size);

getnewbuf() でとにかく buf を取って来る。
bfree(bp) するってことは、いままで使っていたものかも知れないというわけか。 バッファの中身を返しておく。
bremhash() -- binshash() は、hash から外して、新たな値は hash に登録。
brealloc() で、size の buf を得る。
さて、bgetvp(vp,bp) とはなんだろう。

hash にあった場合が後回しになった。

        s = splbio();
        if (bp->b_flags&B_BUSY) {
            bp->b_flags |= B_WANTED;
            sleep((caddr_t)bp, PRIBIO+1);
            splx(s);
            やりなおし
        }

こういう制御構造は パターンか。

s = splbio() --- splx(s) の間で 割込みをマスクして、割込みとの排他制御を行う。 保護しているのは、b_flags 。

おこしてくれというフラグを立てておいて、sleep() 。

ここで使っている sleep は、V7 とまったく同じ 使いかたのようだ。PRIBIO という シンボルまで同じ。

さあ、B_BUSYでないなら、bremfree(bp) する .. え? なんだろう。

@ bread(vp,blkno,size,cred,bpp)

つぎは、bread。

まず getblk(vp,blkno,size) で buf を獲得。

(bp->b_flags&(B_DONE|B_DELWRI)) のときは、すでに内容が入っているから なにもしない。

そうでなければ読んでくる。bp->b_flags |= B_READ; とするから、B_READ は READ 中というフラグ。

つぎに、

    if (bp->b_rcred == NOCRED && cred != NOCRED) {
            crhold(cred);
	    bp->b_rcred = cred;
    }
... これの意味は、今はわからない。

つぎに、VOP_STRATEGY(bp) で I/O 要求。
で biowait(bp) して終ったら戻る。

@ biowait(bp)

    s = splbio();
    while ((bp->b_flags & B_DONE) == 0)
        sleep((caddr_t)bp, PRIBIO);
    splx(s);

説明はいらない、こういう制御構造である。

@ biodone(bp)

これは..I/O が終ったら ... すなわち割込み処理の中で呼ばれるもの。

B_READ でなかったら(... WRITE だったらってこと) いくつか処理をする。

次に、B_CALL がセットされていれば、callback 関数 (bp->b_iodone)(bp) を呼び出す。

さらに、B_ASYNC のときは、brelse(bp)を呼び出す。

次に、B_WANTED の中をチェックすることなく、フラグを落して、 wakeup((caddr_t)bp)

上で出てきた関数の関係って、だいたい次のようになるんだけども

    プロセスの処理                    割込み処理

     bread 
        getblk 
        VOP_VOP_STRATEGY(bp) ----------------------------> I/O が出る 
        :
        iowait
          sleep
            :                           biodone  <-------- 割込みが来る
            :                           wakeup
            <-----------------------------
          sleep 終り
     bread 戻る

     XXX の処理をする

     brelse とかで開放

やっぱり文章だけでイメージせよというのは無理かなぁ。 といって、図 書いてみても、自分のイメージを 正確に表現できないし。
面倒ということもあって、どうも 図を書くのは抵抗がある。

なんとなく... Linux とくらべてみる。

    プロセスの処理                    割込み処理

     bread 
        getblk 
        ll_rw_block(bh) ----------------------------> I/O が出る
        (retern with buffer_locked(bh))  
        wait_on_buffer(bh)
                                              -------- 割込みが来る
          do{                                 V
                                         end_buffer_io_sync
            if (!buffer_locked(bh))         
                 break;                      unlock_buffer(bh)
            schedule() <-----------------       wake_up(&bh->b_wait)   
          }
       wait_on_buffer 終り
     bread 戻る

     XXX の処理をする

     brelse とかで開放

まあ、似たようなものといっていいんだが ... (図では示していないけど) フラグとかリファレンスカウンタとかの 状態の扱いは結構違う。


(最終更新 Thu Mar 30 18:58:34 2006)