bio.c static struct buf* bget(uint dev, uint blockno)

トップページ
jupiteroak.hatenablog.com


bio.c
https://github.com/mit-pdos/xv6-public/blob/master/bio.c#L61

static struct buf*
bget(uint dev, uint blockno)
{
  struct buf *b;

  acquire(&bcache.lock);

  // Is the block already cached?
  for(b = bcache.head.next; b != &bcache.head; b = b->next){
    if(b->dev == dev && b->blockno == blockno){
      b->refcnt++;
      release(&bcache.lock);
      acquiresleep(&b->lock);
      return b;
    }
  }

  // Not cached; recycle an unused buffer.
  // Even if refcnt==0, B_DIRTY indicates a buffer is in use
  // because log.c has modified it but not yet committed it.
  for(b = bcache.head.prev; b != &bcache.head; b = b->prev){
    if(b->refcnt == 0 && (b->flags & B_DIRTY) == 0) {
      b->dev = dev;
      b->blockno = blockno;
      b->flags = 0;
      b->refcnt = 1;
      release(&bcache.lock);
      acquiresleep(&b->lock);
      return b;
    }
  }
  panic("bget: no buffers");
}

bget関数は、引数devと引数blocknoによって指定されるハードディスク上のセクタに対応しているバッファを、バッファキャッシュから取得します。
bget関数はスリープロックした状態のバッファを取得するので、bget関数を呼び出したプロセスがbrelse関数を呼び出すまで、他のプロセスはそのバッファを使用することができません。

引数 uint dev
取得対象となるバッファに対応しているセクタが属するハードディスクドライブを指定します。
引数devが1の時は、マスタードライブを指定しています。
引数devが0の時は、スレイブドライブを指定しています。

引数 uint blockno
取得対象となるバッファに対応しているセクタのセクタ番号を指定します。

戻り値 buf *b または 0
引数devと引数blocknoが指定するセクタに対応しているバッファを取得できた場合は、そのバッファを指定するアドレスが戻り値となります。
引数devと引数blocknoが指定するセクタに対応しているバッファを取得できなかった場合は、0が戻り値となります。


処理の内容

クリティカルセクションの入口を定める

acquire(&bcache.lock);

バッファキャッシュ(bcache構造体)を排他制御するために、acquire関数を呼び出してロックを取得し、クリティカルセクションの入口とします。

バッファキャッシュから指定されたバッファを探す

for(b = bcache.head.next; b != &bcache.head; b = b->next){
    ,,,
  }

バッファキャッシュを走査し、引数devと引数blocknoが指定するセクタに対応しているバッファを探します。
バッファキャッシュは、buf構造体の変数head(bcache構造体のメンバ)とbuf構造体変数の配列buf[NBUF](bcache構造体のメンバ)からなる双方向循環リストです。
bio.c

struct {
  struct spinlock lock;
  struct buf buf[NBUF];

  // Linked list of all buffers, through prev/next.
  // head.next is most recently used.
  struct buf head;
} bcache;

buf.h

struct buf {
  int flags;
  uint dev;
  uint blockno;
  struct sleeplock lock;
  uint refcnt;
  struct buf *prev; // LRU cache list
  struct buf *next;
  struct buf *qnext; // disk queue
  uchar data[BSIZE];
};

param.h

#define MAXOPBLOCKS  10  // max # of blocks any FS op writes
#define NBUF         (MAXOPBLOCKS*3)  // size of disk block cache

クリティカルセクションの出口を定める(指定されたバッファが見つかった場合

 if(b->dev == dev && b->blockno == blockno){
      b->refcnt++;
      release(&bcache.lock);
      acquiresleep(&b->lock);
      return b;
    }

b->dev == dev && b->blockno == blockno が真となる場合→引数devと引数blocknoが指定するセクタに対応しているバッファが見つかった場合は、そのバッファの参照回数をインクリメントします(b->refcnt++;)。
次に、release関数を呼び出して取得していたロックを解放し、クリティカルセクションの出口とします。
最後に、acquiresleep関数を呼び出してバッファに関連しているスリープロックを取得してから、バッファのアドレスを戻り値としてリターンします。

バッファキャッシュから使用頻度が低いバッファを探して再利用する

for(b = bcache.head.prev; b != &bcache.head; b = b->prev){
    ...

引数devと引数blocknoが指定するセクタに対応しているバッファが見つからなかった場合は、使用頻度が低いバッファを再利用します。
バッファキャッシュを逆順でもう一度走査し、一度も参照されていない(b->refcnt == 0 が真)、かつ、保存しているデータをハードディスクへ書き出す必要がない状態((b->flags & B_DIRTY) == 0 が真)のバッファを探します。

クリティカルセクションの出口を定める(該当するバッファが見つかった場合)

    if(b->refcnt == 0 && (b->flags & B_DIRTY) == 0) {
      b->dev = dev;
      b->blockno = blockno;
      b->flags = 0;
      b->refcnt = 1;
      release(&bcache.lock);
      acquiresleep(&b->lock);
      return b;
    }

b->refcnt == 0 && (b->flags & B_DIRTY) == 0 が真となる場合→一度も参照されていない、かつ、保存しているデータをハードディスクへ書き出す必要がない状態のバッファが見つかった場合は、devメンバを引数devで、blocknoメンバを引数blocknoで、flagsメンバを0で、refcntメンバを1で初期化し、ハードディスクから読み込んだデータを保存していない状態、かつ、参照回数が1であるバッファとして、再利用します。flagsメンバを0で初期化するのは、再利用前から保存されているデータが不適切に使用されるのを防ぐためです。
その後、release関数を呼び出して取得していたロックを解放し、クリティカルセクションの出口とします。
最後に、acquiresleep関数を呼び出してバッファに関連しているスリープロックを取得してから、バッファのアドレスを戻り値としてリターンします。

既存のバッファも再利用できるバッファもみつからなかった場合は処理を終了する

panic("bget: no buffers");

既存のバッファも、再利用できるバッファもみつからなかった場合は、panic関数を呼び出してメッセージを出力してから処理を終了します。