zhuzilin's Blog

about

6.828 笔记11 —— File System

date: 2019-03-25
tags: OS  6.828  

这里会记录阅读6.828课程lecture note的我的个人笔记。可能会中英混杂,不是很适合外人阅读,也请见谅。

Lecture 12: File System

我们为什么需要file system

  • durability across restart
  • naming and organization
  • sharing among programs and users

file system有哪些有趣的点?

  • crash memory
  • performance
  • sharing
  • security
  • 各种抽象

xv6实现

之后我们来看一下xv6的实现。因为lecture note里头的东西太乱了,不妨我们直接看xv6 book中对应的内容。

xv6 file system architecture

xv6的fs实现分为7层。从下向上其顺序和功能如下:

  • disk layer: 读写IDE hard drive。
  • buffer cache layer: 做cache存储经常访问的模块,并把并行访问处理成串行访问(感觉有点像database?)
  • logging layer: 提供上述layer的wrapper,从而将多个block的更新分成transaction,并确保crash recovery
  • inode layer: 每个individual files都被表示为一个inode,并有唯一标识符i-number,以及一些block来存储数据。
  • directory layer: 地址,也有i-number和file name
  • pathname layer: 提供像/usr/rtm/xv6/fs.c/usr/rtm/xv6/fs.c这样的地址接口
  • file descriptor layer: 抽象一些unix资源,让他们也能使用file system interface,如pipes, devices...

Buffer layer

buffer layer的代码位于bio.c其主要接口为bwritebread

// 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->flags & B_VALID) == 0) {
    iderw(b);
  }
  return b;
}

// Write b's contents to disk.  Must be locked.
void
bwrite(struct buf *b)
{
  if(!holdingsleep(&b->lock))
    panic("bwrite");
  b->flags |= B_DIRTY;
  iderw(b);
}

更深入一点的话,buf的结构和bget的实现如下:

// buf.h
struct buf {
  int flags;
  uint dev;
  uint blockno;
  struct sleeplock lock;
  uint refcnt;
  struct buf *prev; // LRU cache list
  struct buf *next;
  struct buf *qnext; // disk queue
  uchar data[BSIZE];
};
#define B_VALID 0x2  // buffer has been read from disk
#define B_DIRTY 0x4  // buffer needs to be written to disk

// bio.c
// Look through buffer cache for block on device dev.
// If not found, allocate a buffer.
// In either case, return locked 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");
}

注意这里bget就是从链表里头取一个空的,并不会做MRU (most recent used)的删除操作,如果没有空的就报错。而这个释放的操作是用brelse来进行的。

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

大致就是把引用计数清空,并把这个buf移到链表最前面。

Lecture 13: Crash Recovery, Logging

常见的一个crash recovery的方法是写logging。我们先来看一下xv6的logging,再来了解linux的EXT3