OS-xv6-lab4:锁

实验思想

实验文档说:想要利用hash bucket思想修改缓冲区缓存,来减少对bcache.lock的争用。

在xv6系统中,bcache用于缓存磁盘块,以减少磁盘I/O开销,然而,原始的xv6实现中使用了一个全局锁bcache.lock来保护整个缓冲区缓存系统,这在并发场景中会导致锁竞争严重,限制了多核处理器环境中的并发性能:当前的bcache结构统一管理所有磁盘块缓存,所有对buffer cache的访问都需要加解锁,而当多个CPU核心同时运行多个进程,每个进程都在频繁地访问磁盘块,每个访问都要走缓存块,并发操作buffer cache就会频繁发生这种锁竞争,大多数线程会因为等待bcache.lock而阻塞或自旋浪费CPU时间。

  • hash bucket的思想

    将buffer cache分成多个桶,每个桶有独立的锁,通过哈希函数将不同的block映射到不同的桶中,这样访问块时只需锁对应的桶,锁竞争的概率大幅降低,系统整体可以并发处理多个缓冲块操作,从而实现更细粒度的锁控制。

结构体与变量设置

此处一定要!!!区分BUCKETSIZE和BUFFERSIZE!!!

刚开始实验时因为这俩没区分开,初始化的时候都设为了BUFFERSIZE,一直卡死o(╥﹏╥)o

// bio.c
#define BUCKETSIZE 13 // number of hashing buckets
#define BUFFERSIZE 5 // number of available buskets per busket
extern uint ticks;  
int hash(uint blockno)
{
  return blockno % BUCKETSIZE;
}

struct {
  struct spinlock lock;
  struct buf buf[BUFFERSIZE];
}bcachebucket[BUCKETSIZE];
// buf.h
uint lastuse;
buf结构体
// buf.h
struct buf {
  int valid;   // has data been read from disk?
  int disk;    // does disk "own" buf?
  uint dev;
  uint blockno;
  struct sleeplock lock;
  uint refcnt;
  struct buf *prev; // LRU cache list
  struct buf *next;
  uchar data[BSIZE];

  uint lastuse; //last use time

};
  • valid:表示缓冲区的数据是否有效,是否读取与磁盘

  • dev:设备号

  • blockno:块号

  • lock:睡眠锁

  • prev、next:双向链表中指向前后节点

  • refcnt:记录使用该缓冲块的进程数

初始化

binit函数:使用多个哈希桶bcachebucket进行初始化,每个桶有一个单独的锁bcachebucket[i].lock。每个桶中包含一组缓冲区bcachebucket[i].buf,每个缓冲区都有各自的睡眠锁。

void
binit(void)
{
  for(int i = 0; i < BUCKETSIZE; i++)
  {
    initlock(&bcachebucket[i].lock, "bcachebucket");
    for(int j = 0; j < BUFFERSIZE; j++)
    {
      initsleeplock(&bcachebucket[i].buf[j].lock, "buffer");
      bcachebucket[i].buf[j].lastuse = 0;
      bcachebucket[i].buf[j].refcnt = 0;
    }
  }
}
bpin、bunpin函数

增加、减少缓冲区的引用计数。

修改:将原本的bcache修改为bcachebucket中的bucketno项,其中bucketno为哈希映射后得到的桶号。

void
bpin(struct buf *b) {

  int blockno= hash(b->blockno);

  acquire(&bcachebucket[blockno].lock);
  b->refcnt++;
  release(&bcachebucket[blockno].lock);
}

void
bunpin(struct buf *b) {

  int blobkno = hash(b->blockno);

  acquire(&bcachebucket[blobkno].lock);
  b->refcnt--;
  release(&bcachebucket[blobkno].lock);
}
修改bget函数

根据blockno哈希计算出对应的bucketno后,获取锁,进行查找:遍历这个bucket的缓冲区,如果没能找到dev、blockno都对应的块,则尝试在当前bucket里试图寻找一个空闲的来分配。

首先计算哈希值,获取对应桶的锁。然后遍历该桶的所有缓冲区,检查该块是否已经缓存,如果找到了对应的设备号和块号,则增加refcnt数,更新最近使用时间;如果没有被缓存过,则再次遍历缓冲区,查找没有被使用过且最近最少使用的缓冲区,若成功找到,则更新其相应信息。最后释放桶的锁,获取缓冲区的睡眠锁。若桶内没有找到,则需要在其他的桶内寻找,遍历所有其他的桶的所有缓冲区,一样的判断条件寻找缓存块,这里为了防止过多的循环,我们设置只要在一个桶内找到了符合要求的块就直接赋值,不再遍历所有的。

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

  int bucketno = hash(blockno);

  acquire(&bcachebucket[bucketno].lock);

  // Is the block already cached?
  for(int i = 0; i < BUFFERSIZE; i++)
  {
    b = &bcachebucket[bucketno].buf[i];
    if(b->dev == dev && b->blockno == blockno)
    {
      b->refcnt++;
      b->lastuse = ticks;
      release(&bcachebucket[bucketno].lock);
      acquiresleep(&b->lock);
      return b;
    }
  }

  // Not cached.
  // Recycle the least recently used (LRU) unused buffer.

  int index = -1;
  uint min_ticks = 0xffffffff;
  for(int i = 0; i < BUFFERSIZE; i++)
  {
    b = &bcachebucket[bucketno].buf[i];
    if(b->refcnt == 0 && b -> lastuse < min_ticks)
    {
      index = i;
      min_ticks = b -> lastuse;
    }
  }
  if (index != -1) {
    b = &bcachebucket[bucketno].buf[index];
    b->dev = dev;
    b->blockno = blockno;
    b->valid = 0;
    b->refcnt = 1;
    b->lastuse = ticks;
    release(&bcachebucket[bucketno].lock);
    acquiresleep(&b->lock);
    return b;
  }
  else
  {
    // other bucket
    for (int i = 0; i < BUCKETSIZE; i++) {
      if (i == bucketno)
        continue;
      acquire(&bcachebucket[i].lock);
      for (int j = 0; j < BUFFERSIZE; j++) {
        b = &bcachebucket[i].buf[j];
        if (b->refcnt == 0 && b -> lastuse < min_ticks)
        {
          index = j;
          min_ticks = b -> lastuse;
          break;
        }
      }
      if(index != -1)
      {
        b = &bcachebucket[i].buf[index];
        b -> dev = dev;
        b -> blockno = blockno;
        b -> lastuse = ticks;
        b -> valid = 0;
        b -> refcnt = 1;
        release(&bcachebucket[i].lock);
        acquiresleep(&b->lock);
        return b;
      }
      release(&bcachebucket[i].lock);
    }
  }

  release(&bcachebucket[bucketno].lock);
  panic("bget: no buffers");
}
brelse函数

首先判断是否获取到该块的睡眠锁,若无法获取,即锁被占用,证明这一块还在使用中,不能被释放,报错。正确获取,则证明已经未使用,可以进行设置,首先释放睡眠锁,这块可以再重新被启用。然后根据块号获取哈希桶号,获取桶自旋锁。再将使用进程数减一,没有使用的进程时,更新时间片,然后释放桶的自旋锁。

void
brelse(struct buf *b)
{
  if(!holdingsleep(&b->lock))
    panic("brelse");

  releasesleep(&b->lock);

  int bucketno = hash(b->blockno);
  acquire(&bcachebucket[bucketno].lock);
  b->refcnt--;
  if (b->refcnt == 0) {

    b->lastuse = ticks;
    
  }
  
  release(&bcachebucket[bucketno].lock);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值