Skip to content

Buffer cache(hard)

将缓冲区的分配与回收并行化以提高效率

删除上一个实验多余的lockname

diff --git a/kernel/kalloc.c b/kernel/kalloc.c
index a6203b2..4cd47b5 100644
--- a/kernel/kalloc.c
+++ b/kernel/kalloc.c
@@ -21,7 +21,6 @@ struct run {
 struct {
   struct spinlock lock;
   struct run *freelist;
  char lockname[8];
 } kmem[NCPU];
 
 void

定义哈希桶结构 & 修改binit()函数

删除全局缓冲区链表使用的头结点

diff --git a/kernel/bio.c b/kernel/bio.c
index 60d91a6..7593040 100644
--- a/kernel/bio.c
+++ b/kernel/bio.c
@@ -23,32 +23,49 @@
 #include "fs.h"
 #include "buf.h"
 
#define NBUCKET 13
#define HASH(id) (id % NBUCKET)

struct hashbuf {
  struct buf head;       // 头节点
  struct spinlock lock;  // 锁
};

 struct {
  struct spinlock lock;
  // struct spinlock lock;
   struct buf buf[NBUF];
  struct hashbuf buckets[NBUCKET];  // 散列桶
 
   // Linked list of all buffers, through prev/next.
   // Sorted by how recently the buffer was used.
   // head.next is most recent, head.prev is least.
  struct buf head;
  // struct buf head;
 } bcache;
 
 void
 binit(void)
 {
   struct buf *b;
  char lockname[16];
 
  initlock(&bcache.lock, "bcache");
  for(int i = 0; i < NBUCKET; ++i) {
    // 初始化散列桶的自旋锁
    snprintf(lockname, sizeof(lockname), "bcache_%d", i);
    initlock(&bcache.buckets[i].lock, lockname);

    // 初始化散列桶的头节点
    bcache.buckets[i].head.prev = &bcache.buckets[i].head;
    bcache.buckets[i].head.next = &bcache.buckets[i].head;
  }
 
   // Create linked list of buffers
  bcache.head.prev = &bcache.head;
  bcache.head.next = &bcache.head;
   for(b = bcache.buf; b < bcache.buf+NBUF; b++){
    b->next = bcache.head.next;
    b->prev = &bcache.head;
    // 利用头插法初始化缓冲区列表,全部放到散列桶0上
    b->next = bcache.buckets[0].head.next;
    b->prev = &bcache.buckets[0].head;
     initsleeplock(&b->lock, "buffer");
    bcache.head.next->prev = b;
    bcache.head.next = b;
    bcache.buckets[0].head.next->prev = b;
    bcache.buckets[0].head.next = b;
   }
 }

修改buf.h

提示中建议使用时间戳作为LRU判定的法则

这样就不需要在brelse中进行头插法来更改结点位置

diff --git a/kernel/buf.h b/kernel/buf.h
index 4616e9e..d4fbfa8 100644
--- a/kernel/buf.h
+++ b/kernel/buf.h
@@ -8,5 +8,6 @@ struct buf {
   struct buf *prev; // LRU cache list
   struct buf *next;
   uchar data[BSIZE];
  uint timestamp;
 };

修改brelse()函数 & bpin()函数 & bunpin()函数

由原本的全局锁改为缓存块所在的 bucket 的锁

diff --git a/kernel/bio.c b/kernel/bio.c
index 60d91a6..7593040 100644
--- a/kernel/bio.c
+++ b/kernel/bio.c
@@ -119,35 +183,35 @@ brelse(struct buf *b)
   if(!holdingsleep(&b->lock))
     panic("brelse");
 
  int bid = HASH(b->blockno);

   releasesleep(&b->lock);
 
  acquire(&bcache.lock);
  acquire(&bcache.buckets[bid].lock);
   b->refcnt--;
  if (b->refcnt == 0) {
    // no one is waiting for it.
    b->next->prev = b->prev;
    b->prev->next = b->next;
    b->next = bcache.head.next;
    b->prev = &bcache.head;
    bcache.head.next->prev = b;
    bcache.head.next = b;
  }

  release(&bcache.lock);

  // 更新时间戳
  acquire(&tickslock);
  b->timestamp = ticks;
  release(&tickslock);

  release(&bcache.buckets[bid].lock);
 }
 
 void
 bpin(struct buf *b) {
  acquire(&bcache.lock);
  int bid = HASH(b->blockno);
  acquire(&bcache.buckets[bid].lock);
   b->refcnt++;
  release(&bcache.lock);
  release(&bcache.buckets[bid].lock);
 }
 
 void
 bunpin(struct buf *b) {
  acquire(&bcache.lock);
  int bid = HASH(b->blockno);
  acquire(&bcache.buckets[bid].lock);
   b->refcnt--;
  release(&bcache.lock);
  release(&bcache.buckets[bid].lock);
 }

修改bget()函数

diff --git a/kernel/bio.c b/kernel/bio.c
index 60d91a6..7593040 100644
--- a/kernel/bio.c
+++ b/kernel/bio.c
@@ -60,31 +77,78 @@ bget(uint dev, uint blockno)
 {
   struct buf *b;
 
  acquire(&bcache.lock);
  int bid = HASH(blockno);
  acquire(&bcache.buckets[bid].lock);
 
   // Is the block already cached?
  for(b = bcache.head.next; b != &bcache.head; b = b->next){
    if(b->dev == dev && b->blockno == blockno){
  for(b = bcache.buckets[bid].head.next; b != &bcache.buckets[bid].head; b = b->next) {
    if(b->dev == dev && b->blockno == blockno) {
       b->refcnt++;
      release(&bcache.lock);

      // 记录使用时间戳
      acquire(&tickslock);
      b->timestamp = ticks;
      release(&tickslock);

      release(&bcache.buckets[bid].lock);
       acquiresleep(&b->lock);
       return b;
     }
   }
 
   // Not cached.
  b = 0;
  struct buf* tmp;

   // Recycle the least recently used (LRU) unused buffer.
  for(b = bcache.head.prev; b != &bcache.head; b = b->prev){
    if(b->refcnt == 0) {
  // 从当前散列桶开始查找
  for(int i = bid, cycle = 0; cycle != NBUCKET; i = (i + 1) % NBUCKET) {
    ++cycle;
    // 如果遍历到当前散列桶,则不重新获取锁
    if(i != bid) {
      if(!holding(&bcache.buckets[i].lock))
        acquire(&bcache.buckets[i].lock);
      else
        continue;
    }

    for(tmp = bcache.buckets[i].head.next; tmp != &bcache.buckets[i].head; tmp = tmp->next)
      // 使用时间戳进行LRU算法,而不是根据结点在链表中的位置
      if(tmp->refcnt == 0 && (b == 0 || tmp->timestamp < b->timestamp))
        b = tmp;

    if(b) {
      // 如果是从其他散列桶窃取的,则将其以头插法插入到当前桶
      if(i != bid) {
        b->next->prev = b->prev;
        b->prev->next = b->next;
        release(&bcache.buckets[i].lock);

        b->next = bcache.buckets[bid].head.next;
        b->prev = &bcache.buckets[bid].head;
        bcache.buckets[bid].head.next->prev = b;
        bcache.buckets[bid].head.next = b;
      }

       b->dev = dev;
       b->blockno = blockno;
       b->valid = 0;
       b->refcnt = 1;
      release(&bcache.lock);

      acquire(&tickslock);
      b->timestamp = ticks;
      release(&tickslock);

      release(&bcache.buckets[bid].lock);
       acquiresleep(&b->lock);
       return b;
    } else {
      // 在当前散列桶中未找到,则直接释放锁
      if(i != bid)
        release(&bcache.buckets[i].lock);
     }
   }

   panic("bget: no buffers");
 }

测试

bash
init: starting sh
$ bcachetest
start test0
test0 results:
--- lock kmem/bcache stats                                                       // [!code --]
--- top 5 contended locks:                                                       // [!code --]
lock: virtio_disk: #fetch-and-add 249724 #acquire() 1247
lock: proc: #fetch-and-add 132711 #acquire() 83123
lock: proc: #fetch-and-add 126557 #acquire() 83121
lock: proc: #fetch-and-add 100718 #acquire() 83123
lock: proc: #fetch-and-add 96184 #acquire() 83502
tot= 0
test0: OK
start test1
test1 OK