For the complete documentation index, see llms.txt. This page is also available as Markdown.

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

Was this helpful?