xv6源码分析 016

xv6源码分析 016

之前我们是自顶向下进行分析的(从文件描述符层开始,一直到物理存储层),现在看代码的话我们就是自底向上进行的,因为我们上层使用的接口都实现在下面的层次,如果直接从顶层开始讲的话,很容易就一头雾水的。

let us begin.

buffer cache

物理存储层跳过,因为我看不懂那部分的代码,有关MMIO的,兄弟们可以去了解一下,我现在还在学习这一块

OK,buffer cache层对应的文件就是buf.hbio.c

我们先看看在buf.h中定义的struct buf

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];
};

我们一个一个来看

  • valid:表示这个buf是否有效,是否是从磁盘中读出来的,因为在buffer cache初始化的时候,里面是没有数据的,也就是说所有的buf都是无效的
  • disk:表示当前buf属于那个物理磁盘,对于我们的计算机来说,可能会有多个磁盘,因为要保证存储的稳定性(stable-strode),单个磁盘属于non-volatile strode,但是一个磁盘阵列就属于stable-strode,我们一般成多个磁盘组成的存储系统为冗余磁盘阵列(RAID)。这里不做过多的赘述了,感兴趣的兄弟们可以去了解一下。
  • dev:设备号
  • blockno:块号,如果物理存储器是磁盘则代表磁盘上的逻辑扇区号,如果是SSD,就是块号(主流的就这两种)。
  • lock:维护这个buf不变量的一个自旋锁
  • prev:LRU链表的上一个块
  • next:LRU链表中的下一个块
  • data:数据

我们进入bio.c

bio.c

我们的系统中有很多种io,aio,uio,bio,还有java的nio等等,感兴趣的兄弟们可以去了解一下

先看看核心数据结构

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

  // 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;
} bcache;

很简单,看看就能理解。next one

bcache的初始化
void
binit(void)
{
  struct buf *b;

  initlock(&bcache.lock, "bcache");

  // 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;
    initsleeplock(&b->lock, "buffer");
    bcache.head.next->prev = b;
    bcache.head.next = b;
  }
}

我们可以看到,xv6中是为整个buffer cache维护了一把大锁,导致每一个只能有一个进程访问buffer cache,在后期的lab中,就有一个lab需要我们涉及一个细粒度锁的buffer cache来优化,并支持并发访问

下面就是初始化LRU链表,这里我们需要注意的是,buffer cache只是用bcache.buf来从存放全部的struct buf,但是整个buffer cache是通过一个LRU链表来组织的,这里的数据存储和数据的组织我们需要好好的理解一下。

从buffer cache中获取数据
static struct buf*
bget(uint dev, uint blockno)
{
  struct buf *b;

  acquire(&bcache.lock);

  // Is the block already cached?
  // 先查看我们指定的buf是否已经在内存中了
  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;
    }
  }

  // 如果不在,我们需要找到一个能够存放
  // 我们从物理存储设备中读进来的buf。
  // 如果buffer cache中的所有buf都在使用中
  // 直接panic,哈哈
  // Not cached.
  // Recycle the least recently used (LRU) unused buffer.
  for(b = bcache.head.prev; b != &bcache.head; b = b->prev){
    if(b->refcnt == 0) {
      b->dev = dev;
      b->blockno = blockno;
      b->valid = 0;
      b->refcnt = 1;
      release(&bcache.lock);
      acquiresleep(&b->lock);
      return b;
    }
  }
  panic("bget: no buffers");
}

主要的步骤如下,

  1. 查看指定的数据是否已经被缓存在buffer cache中,我们是怎么指定数据的呢。这依赖于我们上一篇提到的dir层的引导块。后面会将
  2. 如果没有缓存,我们就需要从物理存储设备中读取,这时候,就执行LRU算法并查看算法每一次找到的buf是否正在使用。如果buf这正在使用中,继续执行算法直到找到一个可用的buf,或者到达链表尾部。
  3. 找到之后我们就直接返回这个buf
  4. 如果没有找到我们就panic(死机)
设备io操作

现在我们已经拿到了一个能够存放数据的内存块buf了,接下来自然是从磁盘中读取数据到这个buf上,或者将这个buf上的数据写回磁盘了

看函数

// Return a locked buf with the contents of the indicated block.
struct buf*
bread(uint dev, uint blockno)
{
  struct buf *b;

  b = bget(dev, blockno);
  if(!b->valid) {
    virtio_disk_rw(b, 0);
    b->valid = 1;
  }
  return b;
}

// Write b's contents to disk.  Must be locked.
void
bwrite(struct buf *b)
{
  if(!holdingsleep(&b->lock))
    panic("bwrite");
  virtio_disk_rw(b, 1);
}
释放buf

这个函数将一个已经加锁的buf释放,并从LRU队列中移除。

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

  releasesleep(&b->lock);

  acquire(&bcache.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);
}
pin和unpin

当我们访问一个buf的时候为了避免另一个进程将这个buf驱逐evit,我们需要将这个bufpin住。

void
bpin(struct buf *b) {
  acquire(&bcache.lock);
  b->refcnt++;
  release(&bcache.lock);
}

void
bunpin(struct buf *b) {
  acquire(&bcache.lock);
  b->refcnt--;
  release(&bcache.lock);
}

也很简单就是引用计数的增减。


OK,buffer cache层就讲完了,总的来说还是比较简单,相比于数据库来说。对于数据库中的buffer cache,我给大家安利一波cmu15-445。

  • 11
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值