14.7 Sleep Lock

block cache使用的是sleep lock。sleep lock区别于一个常规的spinlock。我们先看来一下sleep lock。

首先是acquiresleep函数,它用来获取sleep lock。函数里首先获取了一个普通的spinlock,这是与sleep lock关联在一起的一个锁。之后,如果sleep lock被持有,那么就进入sleep状态,并将自己从当前CPU调度开。

既然sleep lock是基于spinlock实现的,为什么对于block cache,我们使用的是sleep lock而不是spinlock?

学生回答:因为磁盘的操作需要很长的时间。

是的,这里其实有多种原因。对于spinlock有很多限制,其中之一是加锁时中断必须要关闭。所以如果使用spinlock的话,当我们对block cache做操作的时候需要持有锁,那么我们就永远也不能从磁盘收到数据。或许另一个CPU核可以收到中断并读到磁盘数据,但是如果我们只有一个CPU核的话,我们就永远也读不到数据了。出于同样的原因,也不能在持有spinlock的时候进入sleep状态(注,详见13.1)。所以这里我们使用sleep lock。sleep lock的优势就是,我们可以在持有锁的时候不关闭中断。我们可以在磁盘操作的过程中持有锁,我们也可以长时间持有锁。当我们在等待sleep lock的时候,我们并没有让CPU一直空转,我们通过sleep将CPU出让出去了。

接下来让我们看一下brelease函数。

brelease函数中首先释放了sleep lock;之后获取了bcache的锁;之后减少了block cache的引用计数,表明一个进程不再对block cache感兴趣;最后如果引用计数为0,那么它会修改buffer cache的linked-list,将block cache移到linked-list的头部,这样表示这个block cache是最近使用过的block cache。这一点很重要,当我们在bget函数中不能找到block cache时,我们需要在buffer cache中腾出空间来存放新的block cache,这时会使用LRU(Least Recent Used)算法找出最不常使用的block cache,并撤回它(注,而将刚刚使用过的block cache放在linked-list的头部就可以直接更新linked-list的tail来完成LRU操作)。为什么这是一个好的策略呢?因为通常系统都遵循temporal locality策略,也就是说如果一个block cache最近被使用过,那么很有可能它很快会再被使用,所以最好不要撤回这样的block cache。

以上就是对于block cache代码的介绍。这里有几件事情需要注意:

  • 首先在内存中,对于一个block只能有一份缓存。这是block cache必须维护的特性。

  • 其次,这里使用了与之前的spinlock略微不同的sleep lock。与spinlock不同的是,可以在I/O操作的过程中持有sleep lock。

  • 第三,它采用了LRU作为cache替换策略。

  • 第四,它有两层锁。第一层锁用来保护buffer cache的内部数据;第二层锁也就是sleep lock用来保护单个block的cache。

最后让我们来总结一下,并把剩下的内容留到下节课。

  • 首先,文件系统是一个位于磁盘的数据结构。我们今天的主要时间都用来介绍这个位于磁盘的数据结构的内容。XV6的这个数据结构实现的很简单,但是你可以实现一个更加复杂的数据结构。

  • 其次,我们花了一些时间来看block cache的实现,这对于性能来说是至关重要的,因为读写磁盘是代价较高的操作,可能要消耗数百毫秒,而block cache确保了如果我们最近从磁盘读取了一个block,那么我们将不会再从磁盘读取相同的block。

下节课我将会介绍crash safety,这是文件系统设计中非常棒的一部分。我们将会在crash safety讲两节课。下节课我们会看到基于log实现的crash safety机制,下下节课我们会看到Linux的ext3是如何实现的logging,这种方式要快得多。

学生提问:我有个关于brelease函数的问题,看起来它先释放了block cache的锁,然后再对引用计数refcnt减一,为什么可以这样呢?

Frans教授:这是个好问题。如果我们释放了sleep lock,这时另一个进程正在等待锁,那么refcnt必然大于1,而b->refcnt --只是表明当前执行brelease的进程不再关心block cache。如果还有其他进程正在等待锁,那么refcnt必然不等于0,我们也必然不会执行if(b->refcnt == 0)中的代码。

Last updated