Skip to content

bio.c

TIP

缓冲区缓存 (Buffer cache) 缓冲区缓存是一个 buf 结构的链表,持有磁盘块内容的缓存副本。 在内存中缓存磁盘块可以减少磁盘读取次数, 并为多个进程使用的磁盘块提供同步点。 接口: * 要获取特定磁盘块的缓冲区,请调用 bread。 * 更改缓冲区数据后,调用 bwrite 将其写入磁盘。 * 使用完缓冲区后,调用 brelse。 * 调用 brelse 后不要再使用该缓冲区。 * 一次只有一个进程可以使用一个缓冲区, 因此不要持有它们超过必要的时间。

#include "types.h"
#include "param.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "riscv.h"
#include "defs.h"
#include "fs.h"
#include "buf.h"

TIP

bcache 结构体定义了缓冲区缓存的全局状态。

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

TIP

所有缓冲区的链表,通过 prev/next 指针连接。 这个链表用于实现 LRU (Least Recently Used) 策略。 head.next 指向最近使用的缓冲区。 head.prev 指向最久未使用的缓冲区。

  struct buf head;
} bcache;

TIP

binit 初始化缓冲区缓存。

void
binit(void)
{
  struct buf *b;

TIP

初始化 bcache 的自旋锁,名称为 "bcache"。

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

TIP

创建缓冲区的双向循环链表。 初始时,head 的 prev 和 next 都指向自己,形成一个空链表。

  bcache.head.prev = &bcache.head;
  bcache.head.next = &bcache.head;

TIP

遍历 buf 数组,将每个缓冲区添加到链表的头部。

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

TIP

bget 在缓冲区缓存中查找一个用于设备 dev 和块号 blockno 的缓冲区。 如果找到了一个匹配的缓冲区,就返回它。 如果没有找到,就从 LRU 链表中找一个未被使用的缓冲区,并将其分配给指定的 dev 和 blockno。 无论哪种情况,返回的缓冲区都是被锁定的。

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

TIP

获取 bcache 的自旋锁,以保护对缓冲区链表的并发访问。

  acquire(&bcache.lock);

TIP

  1. 检查块是否已经被缓存。 从链表头(最近使用的)开始遍历。
  for(b = bcache.head.next; b != &bcache.head; b = b->next){
    if(b->dev == dev && b->blockno == blockno){

TIP

如果找到了匹配的缓冲区,增加其引用计数。

      b->refcnt++;

TIP

释放 bcache 锁,因为已经找到了缓冲区,不再需要操作链表。

      release(&bcache.lock);

TIP

获取该缓冲区的休眠锁。这会使进程休眠,直到该缓冲区可用。

      acquiresleep(&b->lock);

TIP

返回锁定的缓冲区。

      return b;
    }
  }

TIP

  1. 如果块未被缓存。 从链表尾(最久未使用的)开始遍历,寻找一个可以回收的缓冲区。
  for(b = bcache.head.prev; b != &bcache.head; b = b->prev){
    if(b->refcnt == 0) {

TIP

分配这个缓冲区给新的设备和块号。

      b->dev = dev;
      b->blockno = blockno;
      b->valid = 0;
      b->refcnt = 1;

TIP

释放 bcache 锁。

      release(&bcache.lock);

TIP

获取该缓冲区的休眠锁。

      acquiresleep(&b->lock);

TIP

返回新分配并锁定的缓冲区。

      return b;
    }
  }
  

TIP

如果所有缓冲区都已被引用,说明系统资源不足。

  panic("bget: no buffers");
}

TIP

bread 返回一个锁定的缓冲区,其中包含设备 dev 上块 blockno 的内容。 如果缓冲区内容无效,它会从磁盘读取数据。

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

TIP

获取一个用于指定设备和块号的缓冲区。

  b = bget(dev, blockno);

TIP

检查缓冲区的数据是否有效。

  if(!b->valid) {

TIP

如果数据无效,调用 virtio_disk_rw 从磁盘读取数据到缓冲区。 第二个参数 0 表示读取操作。

    virtio_disk_rw(b, 0);

TIP

将缓冲区标记为有效。

    b->valid = 1;
  }

TIP

返回包含有效数据的锁定缓冲区。

  return b;
}

TIP

bwrite 将缓冲区 b 的内容写入磁盘。 调用者必须持有该缓冲区的锁。

void
bwrite(struct buf *b)
{

TIP

确保调用者持有缓冲区的休眠锁。

  if(!holdingsleep(&b->lock))
    panic("bwrite");

TIP

调用 virtio_disk_rw 将缓冲区数据写入磁盘。 第二个参数 1 表示写入操作。

  virtio_disk_rw(b, 1);
}

TIP

brelse 释放一个锁定的缓冲区。 它会减少缓冲区的引用计数,并在引用计数为零时将其移动到 LRU 链表的头部。

void
brelse(struct buf *b)
{

TIP

确保调用者持有缓冲区的休眠锁。

  if(!holdingsleep(&b->lock))
    panic("brelse");

TIP

释放缓冲区的休眠锁,允许其他进程使用它。

  releasesleep(&b->lock);

TIP

获取 bcache 的自旋锁,以安全地修改引用计数和链表。

  acquire(&bcache.lock);

TIP

减少引用计数。

  b->refcnt--;
  if (b->refcnt == 0) {

TIP

如果没有其他进程引用此缓冲区, 将其移动到 LRU 链表的头部(最近使用的位置)。

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

TIP

释放 bcache 锁。

  release(&bcache.lock);
}

TIP

bpin 增加缓冲区的引用计数。 这可以防止缓冲区被 bget 回收。

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

TIP

bunpin 减少缓冲区的引用计数。

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