虚拟缓存存在一个很大的问题,由于每一个进程都拥有独立的地址空间。当上下文切换后,必须对缓存进行刷新。一个很简单的想法就是把pid和虚拟地址结合起来作为索引进行缓存查找,从而减少刷新缓存的次数。

带有键值的虚拟缓存的基本操作

首先是硬件结构的变化,由于需要支持键值,每个cache line的tag都会增加一个进程键值用于区分不同的进程。同时我们需要一个新的寄存器来保存这个进程键值,如下图所示:

带有键值的虚拟缓存

带有键值的虚拟缓存除了其加了一个新的键值以外其本质与虚拟缓存的操作没有差别。其仍然通过虚拟地址进行索引,然而由于有额外的键值信息可以判断当前缓存是否属于当前进程。一个美好的愿望是硬件为系统中的每一个进程都生成一个独一无二的标识符,这样我们就不会产生歧义和别名。愿望始终是愿望,硬件能够提供的标识符往往数目很少,有些硬件仅仅支持8个标识符。所以会有某些进程共享同一个标识符,我们仍然会面临虚拟缓存的歧义和别名问题。

操作系统与带有键值的虚拟缓存的交互

上下文切换

当键值充足时,我们可以为每一个在系统中运行的进程分配一个独一无二的键,这样上下文切换的时候,对于write-through而言,我们都不需要刷新缓存,仅仅只需要改变寄存器中的进程键值即可。

当键值不够使用时,操作系统需要重新分配键值。一个常见的思路是换入和换出,我们仍然需要将某个键值换出,然后把所有有关于该键值的缓存进行刷新。对于write-through而言,我们做的操作会比较简单,我们直接可以直接把cache line的valid位置为0。对于write-back而言,我们还需要写入内存。实际上,你会发现,这样也带来了另外的复杂度,操作系统应该换出哪个键呢?

对于采取write-back的写策略,还有一个更加严重的问题。当我们对进程A进行了上下文切换后,其cache line的值可能已经被改变了,然而很有可能另一个进程B会刷新这个cache line,但是对于进程B而言,操作系统并没有进程A的页表信息,操作系统无法知道该写入哪个具体的物理内存。因此,大多数采取带有键值的虚拟缓存会采用write-through策略。

有些硬件提供了额外的机制,既然目前已经有键值信息,就可以把键值信息和页表信息组成一个映射,这样对于上述情况,就能够找到物理地址,从而写入数据。然而,却极大地增加了硬件负担。

共享内存

对于虚拟缓存而言,两个进程之间共享内存会因为上下文切换时自动对缓存进行刷新的机制避免别名问题,然而带有键值的虚拟内存会更加的复杂。

对于直接映射缓存而言,我们假设两个进程的相同的虚拟地址指向相同的物理地址。同时假设操作系统给进程$A$分配的键值为1,给进程$B$分配的键值为2。我们可以明确地知道由于虚拟地址相同,其找到的cache set必然相同,由于每个cache set只有一个cache line。如下图所示,当进程A切换到进程B时,进程B访问该虚拟地址,由于键值不同,会导致cache miss,然后读取相同的物理内存到cache中。然而,物理内存没有发生任何的改变。

共享内存-直接映射虚拟缓存

然而,大多数情况这两个进程的虚拟地址是不相同的,这样就和虚拟缓存的情况一样了,会产生别名的情况。当我们需要使用共享内存时,cache在一定程度上就失效了。

应用

MMU的一个重要作用就是将虚拟地址转化为物理地址。MMU内部提供了自动将虚拟地址转化为物理地址的机制,操作系统通过构建虚拟地址到物理地址的映射,对于每个进程操作系统都会构建页表的基地址将其放到某个特定的寄存器中。

MMU与操作系统的交互

Sv39 RISC-V只使用虚拟地址的低39位作为索引来翻译物理地址。如下图所示,其使用了三层页表结构来将虚拟地址翻译为物理地址。其转化的过程相当简单,首先通过页表的基地址找到其L2的偏移量,然后读取其值找到二级页表的基地址找到其L1的偏移量,然后这样循环地进行找到物理地址加上虚拟地址中的偏移量即可。

RISC-V address translation details

对于操作系统,只需要将页表的基地址放入satp寄存器中即可。硬件会自动完成虚拟地址到物理地址的转换。

TLB

从上述的过程可以得知,既然硬件需要完成虚拟地址到物理地址,我们已经讲述过的虚拟缓存是不是完美符合这个需求的。现有的MMU都有一小块虚拟缓存,即TLB。TLB保存的缓存信息即为虚拟地址对应的物理地址以及页属性。由于不同进程的页表不同,其虚拟地址对应的物理地址也必然不同:

  • 对于虚拟缓存来说,每次进行上下文切换都需要将对应的cache line valid位置0。
  • 对于带有键值的虚拟缓存来说,由于其键值存在,不需要每次上下文切换刷新TLB,当键值不够分配时,才需要将对应的cache line valid位置0。

TLB是最适合使用虚拟缓存的,这是因为TLB是一个只读缓存且没有共享内存机制。

基于物理标签的虚拟缓存

虚拟缓存最重要的一个问题在于虚拟地址与物理地址的映射问题,虚拟地址是操作系统与硬件结合起来给进程提供的抽象,其映射关系会随着上下文切换发生改变,从而造成了别名和歧义问题。于是就提出了另一种类型的虚拟缓存,我们仍然使用虚拟缓存对cache se定位,同时找寻block offset。但是缓存中的tag使用物理地址。然而,这样我们不得不增加新的负担,也就是我们必须用通过MMU将虚拟地址转化为物理地址。

参考资料