> For the complete documentation index, see [llms.txt](https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081/lec10-multiprocessors-and-locking/10.1-why-lock.md).

# 10.1 为什么要使用锁？

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

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

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

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

![](/files/-MP1nThbSUkBbIEP7im3)

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

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

![](/files/-MP1ob-bZUIk-FbzxxWC)

这张图有点复杂，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中。

![](/files/-MP6weP3Xb8oQ96iC3XL)

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

![](/files/-MP6xsdFTj1ii2LA7xLo)

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

![](/files/-MP6yOCT-c0K8Mu8_eyj)

我们可以看到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运行并不会有问题。

![](/files/-MPC5RCxF-Zt7XfayFdn)

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