xv6——文件系统:磁盘的I/O操作和内存缓存机制

目录


作者:殷某人
更新时间:2022/07/03

相关源码文件

buf.h
bio.c
ide.c

架构图

数据结构

内存的缓存块

缓存块用于缓存磁盘上的一个block, 大小为512字节, 在内存中,缓存块采用数组+双向链表的数据结构,使用链表的目的是进行LRU优化, 链表的顺序按照最近使用顺序进行排列, 提高复用率,减少IO操作。
每一个buffer块都有自己的一把锁,一个buffer块同一时间只能有一个进程拥有。

  • buffer块的数据结构定义如下:
struct buf {
  int flags;             // 标志位,b_valid和b_dirty.
  uint dev;              // 缓存块对应的磁盘设备号
  uint blockno;          // 缓存块对应的block块号
  struct sleeplock lock; // 睡眠锁, 保证一个buffer同一时间只可能被一个进程拥有
  uint refcnt;           // 引用次数
  struct buf *prev;      // LRU双向链表
  struct buf *next;      // LRU双向链表
  struct buf *qnext;     // 当Buffer块需要与磁盘间进行同步时,Buffer块之间组成的单向同步队列
  uchar data[BSIZE];     // 512字节的数据缓存区
};
  • buffer Table的数据结构定义如下:
struct {
  struct spinlock lock;   // 锁
  struct buf buf[NBUF];   // buffer块数组,一大块连续的buffer
  struct buf head;        // 它可以看出一个哨兵,目的方便双向链表的操作
                          // head.next 是第一个buffer块, 它是最近使用过的!
} bcache;

函数实现

IDE磁盘的读写操作

磁盘串口读写操作函数 void idestart()

  • 计算要主读取的sector块数,通常512字节对应一个sector.
  • 写操作:CPU只负责写到磁盘的串口寄存器, 磁盘自己会执行真正的写到磁盘的操作.完成之后,会通过中断告诉CPU
  • 读操作:CPU通知磁盘读取512字节内容到磁盘寄存器,完成之后,通过中断告诉CPU,CPU之后会把内容复制到内存中
static void idestart(struct buf *b)
{
  if(b == 0)
    panic("idestart");
  if(b->blockno >= FSSIZE)
    panic("incorrect blockno");
  int sector_per_block =  BSIZE/SECTOR_SIZE;
  int sector = b->blockno * sector_per_block;
  int read_cmd = (sector_per_block == 1) ? IDE_CMD_READ :  IDE_CMD_RDMUL;
  int write_cmd = (sector_per_block == 1) ? IDE_CMD_WRITE : IDE_CMD_WRMUL;

  if (sector_per_block > 7) panic("idestart");

  idewait(0);
  outb(0x3f6, 0);  // generate interrupt
  outb(0x1f2, sector_per_block);  // number of sectors
  outb(0x1f3, sector & 0xff);
  outb(0x1f4, (sector >> 8) & 0xff);
  outb(0x1f5, (sector >> 16) & 0xff);
  outb(0x1f6, 0xe0 | ((b->dev&1)<<4) | ((sector>>24)&0x0f));

  if(b->flags & B_DIRTY){ 
    outb(0x1f7, write_cmd);
    outsl(0x1f0, b->data, BSIZE/4);
  } else {
    outb(0x1f7, read_cmd);
  }
}

磁盘中断处理函数void ideintr()

  • 在收到中断处理函数时, 如果是磁盘读取工作完成,会负责把内容从寄存器读取到内存缓存区。
  • 刷新标志位,清掉dirdy, 置位valid.

void ideintr(void)
{
  struct buf *b;
	
  // First queued buffer is the active request.
  acquire(&idelock);
	
  if((b = idequeue) == 0){
    release(&idelock);
    return;
  }
  idequeue = b->qnext;

  // Read data if needed.
  if(!(b->flags & B_DIRTY) && idewait(1) >= 0)
    insl(0x1f0, b->data, BSIZE/4);

  // Wake process waiting for this buf.
  b->flags |= B_VALID;
  b->flags &= ~B_DIRTY;
  wakeup(b);

  // Start disk on next buf in queue.
  if(idequeue != 0)
    idestart(idequeue);

  release(&idelock);
}

磁盘的读写处理函数void iderw()

  • 把需要同步的buffer块添加到操作队列中
  • 如果队列内只有它自己,说明队列任务未启动, 启动磁盘同步。
void iderw(struct buf *b)
{
  struct buf **pp;

  if(!holdingsleep(&b->lock))
    panic("iderw: buf not locked");
  if((b->flags & (B_VALID|B_DIRTY)) == B_VALID)
    panic("iderw: nothing to do");
  if(b->dev != 0 && !havedisk1)
    panic("iderw: ide disk 1 not present");

  acquire(&idelock);  //DOC:acquire-lock

  // Append b to idequeue.
  b->qnext = 0;
  for(pp=&idequeue; *pp; pp=&(*pp)->qnext)  //DOC:insert-queue
    ;
  *pp = b;

  // Start disk if necessary.
  if(idequeue == b)
    idestart(b);

  // Wait for request to finish.
  while((b->flags & (B_VALID|B_DIRTY)) != B_VALID){
    sleep(b, &idelock);
  }

  release(&idelock);
}

内存缓存区操作函数

Buffer块初始化函数void binit()

  • 初始化锁
  • 初始化双向链表
void binit(void)
{
  struct buf *b;

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

//PAGEBREAK!
  // 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;
  }
}

查找指定的磁盘block块对应的buffer缓存块函数struct buff* bget()

  • 如果该bock块已经缓存过,直接返回缓存的buffer块
  • 如果没有缓存过,查找一块空闲的buffer块,然后返回。
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");
}

读取指定的磁盘的block块, 返回读取到的buffer块bread()

  • 如果该bock块已经缓存过,直接返回缓存的buffer块,它当前数据是最新的。
  • 如果没有缓存过,需要把磁盘上的block块的内容同步到缓存块中,然后返回。
struct buf*
bread(uint dev, uint blockno)
{
  struct buf *b;

  b = bget(dev, blockno);
  if((b->flags & B_VALID) == 0) {
    iderw(b);
  }
  return b;
}

写缓存块操作 bwrite()

  • 写之前,该buffer块一定是已经加锁的。
  • 写完成之后,会执行缓存区到磁盘的写操作。
void
bwrite(struct buf *b)
{
  if(!holdingsleep(&b->lock))
    panic("bwrite");
  b->flags |= B_DIRTY;
  iderw(b);
}

释放指定的buffer块 void brelse()

  • 释放锁, 减少引用次数
  • 若该buffer没有进程使用时,把它移动到链接头部,LRU。
// Release a locked buffer.
// Move to the head of the MRU list.
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);
}
posted @ 2022-07-10 23:27  殷大侠  阅读(418)  评论(0编辑  收藏  举报