> 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/lec22-meltdown-robert/22.4-cpu-caches.md).

# 22.4 CPU caches

接下来我将介绍Micro-Architectural的另一个部分，也就是缓存。我知道大家都知道CPU有cache，但是缓存或多或少应该是也透明的。让我画个图描述一下cache，因为我认为cache与Meltdown最相关。

首先，你有CPU核，这是CPU的一部分，它会解析指令，它包含了寄存器，它有加法单元，除法单元等等。所以这是CPU的执行部分。

![](/files/-MYbboQtuYIubSTAvs8w)

当CPU核需要执行load/store指令时，CPU核会与内存系统通信。内存系统一些cache其中包含了数据的缓存。首先是L1 data cache，它或许有64KB，虽然不太大，但是它特别的快。如果你需要的数据在L1 cache中，只通过几个CPU cycle就可以将数据取回。L1 cache的结构包含了一些线路，每个线路持有了可能是64字节的数据。这些线路是个表单，它们通过虚拟内存地址索引。如果一个虚拟内存地址在cache中，并且cache为这个虚拟内存地址持有了数据，那么实际中可以认为L1 cache中也包含了来自对应于虚拟内存地址的PTE的权限。

![](/files/-MYbbuaqRdp8YTMN2Kiu)

L1 cache是一个表单，当CPU核执行load指令时，首先硬件会检查L1 cache是否包含了匹配load指令的虚拟内存地址，如果有的话，CPU会直接将L1 cache中的数据返回，这样可以很快完成指令。

如果不在L1 cache，那么数据位于物理内存中，所以现在我们需要物理内存地址，这里需要Translation Lookaside Buffer（TLB），TLB是PTE的缓存。现在我们会检查load指令中的虚拟内存地址是否包含在TLB中。如果不在TLB，我们就需要做大量的工作，我们需要从内存中读取相关的PTE。让我们假设TLB中包含了虚拟内存地址对应的物理内存Page地址，我们就可以获取到所需要的物理内存地址。通常来说会有一个更大的cache（L2 cache），它是由物理内存地址索引。

![](/files/-MYbdf0xpbZ7gsqIhhkU)

现在通过TLB我们找到了物理内存地址，再通过L2 cache，我们有可能可以获取到数据。如果我们没有在L2 cache中找到物理内存地址对应的数据。我们需要将物理内存地址发送给RAM系统。这会花费很长的时间，当我们最终获得了数据时，我们可以将从RAM读取到的数据加入到L1和L2 cache中，最终将数据返回给CPU核。

![](/files/-MYbeMrWmy5CizaDr2-r)

以上就是CPU的cache。如果L1 cache命中的话可能只要几个CPU cycle，L2 cache命中的话，可能要几十个CPU cycle，如果都没有命中最后需要从内存中读取那么会需要几百个CPU cycle。一个CPU cycle在一个2GHZ的CPU上花费0.5纳秒。所以拥有cache是极其有利的，如果没有cache的话，你将会牺牲掉几百倍的性能。所以cache对于性能来说是非常关键的。

在Meltdown Attack的目标系统中，如果我们运行在用户空间，L1和L2 cache可以既包含用户数据，也包含内核数据。L2 cache可以包含内核数据因为它只是物理内存地址。L1 cache有点棘手，因为它是虚拟内存地址，当我们更换Page Table时，L1 cache的内容不再有效。因为更换Page Table意味着虚拟内存地址的意义变了，所以这时你需要清空L1 cache。不过实际中会有更多复杂的细节，可以使得你避免清空L1 cache。

论文中描述的操作系统并没有在内核空间和用户空间之间切换的时候更换Page Table，因为两个空间的内存地址都映射在同一个Page Table中了。这意味着我们不必清空L1 cache，也意味着L1 cache会同时包含用户和内核数据，这使得系统调用更快。如果你执行系统调用，当系统调用返回时，L1 cache中还会有有用的用户数据，因为我们在这个过程中并没与更换Page Table。所以，当程序运行在用户空间时，L1 cache中也非常有可能有内核数据。L1 cache中的权限信息拷贝自TLB中的PTE，如果用户空间需要访问内核内存数据，尽管内核数据在L1 cache中，你也不允许使用它，如果使用的话会触发Page Fault。

尽管Micro-Architectural的初衷是完全透明，实际中不可能做到，因为Micro-Architectural优化的意义在于提升性能，所以至少从性能的角度来说，它们是可见的。也就是说你可以看出来你的CPU是否有cache，因为如果没有的话，它会慢几百倍。除此之外，如果你能足够精确测量时间，那么在你执行一个load指令时，如果load在几个CPU cycle就返回，数据必然是在cache中，如果load在几百个CPU cycle返回，数据可能是从RAM中读取，如果你能达到10纳秒级别的测量精度，你会发现这里区别还是挺大的。所以从性能角度来说，Micro-Architectural绝对不是透明的。我们现在讨论的分支预测，cache这类功能至少通过时间是间接可见的。

所以尽管Micro-Architectural设计的细节都是保密的，但是很多人对它都有强烈的兴趣，因为这影响了很多的性能。比如说编译器作者就知道很多Micro-Architectural的细节，因为很多编译器优化都基于人们对于CPU内部工作机制的猜测。实际中，CPU制造商发布的优化手册披露了一些基于Micro-Architectural的技巧，但是他们很少会介绍太多细节，肯定没有足够的细节来理解Meltdown是如何工作的。所以Micro-Architectural某种程度上说应该是透明的、隐藏的、不可见的，但同时很多人又知道一些随机细节。

（以下内容来自82:10 - 86:30，因为相关故移过来了）

> 学生提问：L1 cache是每个CPU都有一份，L2 cache是共享的对吧？
>
> Robert教授：不同CPU厂商，甚至同一个厂商的不同型号CPU都有不同的cache结构。今天普遍的习惯稍微有点复杂，在一个多核CPU上，每一个CPU核都有一个L1 cache，它离CPU核很近，它很快但是很小。每个CPU核也还有一个大点的L2 cache。除此之外，通常还会有一个共享的L3 cache。

![](/files/-MYyv9j2NEJvFLz-8p8E)

> 另一种方式是所有的L2 cache结合起来，以方便所有的CPU共用L2 cache，这样我可以非常高速的访问我自己的L2 cache，但是又可以稍微慢的访问别的CPU的L2 cache，这样有效的cache会更大。

![](/files/-MYyvLF8spLlC0_O3X2L)

> 所以通常你看到的要么是三级cache，或者是两级cache但是L2 cache是合并在一起的。典型场景下，L2和L3是物理内存地址索引，L1是虚拟内存地址索引。
>
> 学生提问：拥有物理内存地址的缓存有什么意义？
>
> Robert教授：如果同一个数据被不同的虚拟内存地址索引，虚拟内存地址并不能帮助你更快的找到它。而L2 cache与虚拟内存地址无关，不管是什么样的虚拟内存地址，都会在L2 cache中有一条物理内存地址记录。
>
> 学生提问：MMU和TLB这里位于哪个位置？
>
> Robert教授：我认为在实际中最重要的东西就是TLB，并且我认为它是与L1 cache并列的。如果你miss了L1 cache，你会查看TLB并获取物理内存地址。MMU并不是一个位于某个位置的单元，它是分布在整个CPU上的。

![](/files/-MYyxfoamkUalbTOzKZC)

> 学生提问：但是MMU不是硬件吗？
>
> Robert教授：是的，这里所有的东西都是硬件。CPU芯片有数十亿个晶体管，所以尽管是硬件，我们讨论的也是使用非常复杂的软件设计的非常复杂的硬件。所以CPU可以做非常复杂和高级的事情。所以是的，它是硬件，但是它并不简单直观。
>
> 学生提问：Page Table的映射如果没有在TLB中命中的话，还是要走到内存来获取数据，对吧？
>
> Robert教授：从L2 cache的角度来说，TLB miss之后的查找Page Table就是访问物理内存，所以TLB需要从内存中加载一些内存页，因为这就是加载内存，这些内容可以很容易将Page Table的内容缓存在L2中。
