10.1 为什么要使用锁?

(00:00 - 01:33)是上一个lab的抽查问答,与内容无关故跳过。

今天的课程的内容是锁。这节课偏向于理论介绍,并且或许会与其他课程中有关锁的内容有些重合,不过这节课更关注在内核和操作系统中使用的锁。

首先,我们来看一下为什么我们需要锁?故事要从应用程序想要使用多个CPU核开始。使用多个CPU核可以带来性能的提升,如果一个应用程序运行在多个CPU核上,并且执行了系统调用,那么内核需要能够处理并行的系统调用。如果系统调用并行的运行在多个CPU核上,那么它们可能会并行的访问内核中共享的数据结构。到目前为止,你们也看到了XV6有很多共享的数据结构,例如proc、ticks和我们之后会看到的buffer cache等等。当并行的访问数据结构时,例如一个核在读取数据,另一个核在写入数据,我们需要使用锁来协调对于共享数据的更新,以确保数据的一致性。所以,我们需要锁来控制并确保共享的数据是正确的。

但是实际的情况有些令人失望,因为我们想要通过并行来获得高性能,我们想要并行的在不同的CPU核上执行系统调用,但是如果这些系统调用使用了共享的数据,我们又需要使用锁,而锁又会使得这些系统调用串行执行,所以最后锁反过来又限制了性能。

所以现在我们处于一个矛盾的处境,出于正确性,我们需要使用锁,但是考虑到性能,锁又是极不好的。这就是现实,我们接下来会看看如何改善这个处境。

以上是一个大概的介绍,但是回到最开始,为什么应用程序一定要使用多个CPU核来提升性能呢?这个实际上与过去几十年技术的发展有关,下面这张非常经典的图可以解释为什么。

这张图有点复杂,X轴是时间,Y轴是单位,Y轴具体意义取决于特定的曲线。这张图中的核心点是,大概从2000年开始:

  • CPU的时钟频率就没有再增加过了(绿线)。

  • 这样的结果是,CPU的单线程性能达到了一个极限并且也没有再增加过(蓝线)。

  • 但是另一方面,CPU中的晶体管数量在持续的增加 (深红色线)。

  • 所以现在不能通过使用单核来让代码运行的更快,要想运行的更快,唯一的选择就是使用多个CPU核。所以从2000年开始,处理器上核的数量开始在增加(黑线)。

所以现在如果一个应用程序想要提升性能,它不能只依赖单核,必须要依赖于多核。这也意味着,如果应用程序与内核交互的较为紧密,那么操作系统也需要高效的在多个CPU核上运行。这就是我们对内核并行的运行在多个CPU核上感兴趣的直接原因。你们可能之前已经看过上面这张图,但我们这里回顾一下背景知识也是极好的。

那为什么要使用锁呢?前面我们已经提到了,是为了确保正确性。当一份共享数据同时被读写时,如果没有锁的话,可能会出现race condition,进而导致程序出错。race condition是比较讨厌的,我们先来看看什么是race condition。我们接下来会在XV6中创建一个race condition,然后看看它的表象是什么。

kalloc.c文件中的kfree函数会将释放的page保存于freelist中。

freelist是XV6中的一个非常简单的数据结构,它会将所有的可用的内存page保存于一个列表中。这样当kalloc函数需要一个内存page时,它可以从freelist中获取。从函数中可以看出,这里有一个锁kmem.lock,在加锁的区间内,代码更新了freelist。现在我们将锁的acquire和release注释上,这样原来在上锁区间内的代码就不再受锁保护,并且不再是原子执行的。

之后运行make qemu重新编译XV6,

我们可以看到XV6已经运行起来,并且我们应该已经运行了一些对于kfree的调用,看起来一切运行都正常啊。

接下来运行一下usertest,究竟能不能成功呢?有人想猜一下吗?

学生回答:如果发生了race condition就会丢失一些内存page,如果没有发生就能成功。

是的,race condition不一定会发生,让我们来运行一下usertest,看看究竟会发生什么。我这里通过qemu模拟了3个CPU核,这3个核是并行运行的。但是如刚刚那位同学指出的,race condition不一定会发生,因为当每一个核在每一次调用kfree函数时,对于freelist的更新都还是原子操作,这与有锁是一样,这个时候没有问题。有问题的是,当两个处理器上的两个线程同时调用kfree,并且交错执行更新freelist的代码。

我们来看一下usertest运行的结果,可以看到已经有panic了。所以的确有一些race condition触发了panic。但是如前面的同学提到的,还有一些其他的race condition会导致丢失内存page,这种情况下,usertest运行并不会有问题。

所以race condition可以有不同的表现形式,并且它可能发生,也可能不发生。但是在这里的usertests中,很明显发生了什么。

Last updated