【问题标题】:cache coherency (particular case of cache physically tagged)缓存一致性(物理标记的缓存的特殊情况)
【发布时间】:2020-06-11 13:34:35
【问题描述】:

假设您有一个已完成的进程(现在不在内存中),但是当它运行时,它使用了 0x12345000 物理地址(4KB 页)。现在 MMU 将 0x12345000(物理)分配给另一个刚刚启动的进程。 但也许你在缓存(物理标记)中有前一个过程的数据的 0x12345 标记。这是一个连贯性问题。怎么解决的?

编辑:假设是:一个进程完成,另一个进程从磁盘传送到内存到内存的同一页运行。我的问题是:如何防止出现问题?我了解到,在将第二个进程带入内存之前,页面已清零。所以现在在缓存中我们有对应于该页面的零。但是页面有第二个进程的数据。这就是我所理解的,但可能是错误的。

Peter Cordes 的回答很完美!

【问题讨论】:

  • 我在这里没有发现问题。操作系统可以让新进程看到前一个进程留在该页面中的任何陈旧数据,或者通过缓存命中,或者如果脏数据被写回,则通过从 RAM 填充。或者更可能在实际操作系统中,内核将在将物理页面(通过其虚拟地址)归零之前将其提供给新进程,以避免跨进程(可能还有用户)边界泄漏数据。在多核系统上,一致性由 MESI 或某种变体维护,因此无论新进程在哪个核上运行,缓存是否热都无关紧要。
  • 彼得,我看到的问题是新进程在缓存中看到它想要访问的内存位置已经在缓存中(这会导致缓存命中)。但是要从缓存中使用的数据不是正确的数据,它是前一个进程的数据,映射到该物理方向,并且其数据仍保留在缓存中。 (我是 stackoverflow 的新手,我不知道这是否是回复您评论的最佳方式。我希望它能传达给您)。
  • 您可以在使用@username 回复时通知他人。有时 Stack Overflow 会发送通知,就像在这种情况下一样,但最好确定一下,除非您在其他人的帖子下发表评论(然后他们总是会收到通知)。此外,“缓存”在 e 上没有重音。计算的含义来自名词merriam-webster.com/dictionary/cache,意思是隐藏的供应品(“食物的缓存”),它的发音与“现金”相同。不是cachet / caché 意味着声望。
  • 回到您的问题:我认为您在这里混淆了两个概念:即使在没有缓存的系统中,操作系统通常也需要先将物理页面归零(或将新数据加载到其中)使用它来支持不同进程中的虚拟页面。如果没有,新进程将看到其中留下的任何陈旧数据。缓存与此无关。 PIPT 缓存对 CPU 完全透明(没有混叠问题);它基于物理地址进行缓存,因此即使操作系统将同一页面映射到另一个进程中的不同 virtual 地址,它也不会在意。
  • 此外,“一致性”仅适用于同一内存的两个缓存之间,例如在多核 CPU 中。 en.wikipedia.org/wiki/Cache_coherence。也许您正在考虑缓存别名问题? (Definition/meaning of Aliasing? (CPU cache architectures))。 PIPT 缓存不受别名的影响,并且将相同的页面映射到相同的虚拟地址也使得 VIPT 甚至 VIVT 缓存不可能出现别名。缓存中的任何热行都将准确反映该物理内存页面中的内容。

标签: caching operating-system x86-64 cpu-cache dma


【解决方案1】:

但缓存中剩余的数据来自上一个进程

是的,这就是应该发生的。缓存只是跟踪物理内存中的内容。这是它唯一的工作。它不知道进程。

如果操作系统不希望新进程看到该数据,内核需要运行一些指令将新数据存储到该页面,从而覆盖缓存和内存内容。

缓存对这个操作是透明的;数据在缓存中是否仍然是热的,或者在内核重用该物理页面时旧进程的数据是否已写回 RAM 都无关紧要。

(有关更多详细信息,另请参阅问题下的 cmets)。

我知道操作系统会将物理页面归零,但这是在主内存中,但我说的是缓存内存中的残留数据。

我认为 this 是您困惑的根源:这种归零发生在 CPU 执行的普通存储指令中。 操作系统在 CPU 上运行,并将归零通过循环存储零的字节(或字)来创建一个页面。这些存储是普通的可缓存存储,与进入缓存/内存层次结构顶部的任何其他写入相同。

如果操作系统想要将归零卸载到缓存不一致的 DMA 引擎或 blitter 芯片,那么是的,操作系统必须首先使该页面中的任何缓存行无效以避免您正在谈论的问题,失去与 RAM 的连贯性。但这不是正常情况。


顺便说一句,“普通商店”仍然可以很快。例如现代 x86 CPU 可以使用 SIMD 指令或 rep stosb 以每个时钟周期存储 32 或 64 个字节,rep stosb 基本上是一个可以在内部使用宽存储的微编码 memset。 AMD 甚至有一条clzero 指令将一个完整的高速缓存行归零。但这些仍然是 CPU 指令,其内存视图通过缓存。


为新进程加载新代码/数据

现代 x86-64 系统具有缓存一致的 DMA,因此这不是问题。当内存控制器内置在 CPU 中时,这在现代 x86-64 中很容易,因此 PCIe 流量可以在过去的路上检查 L3 缓存。前一个进程的缓存中哪些缓存行仍然是热的并不重要;进入该页面的 DMA 将这些行从缓存中逐出。 (或者使用非 DMA“编程 IO”,数据实际上是由运行在 CPU 内核上的驱动程序代码加载到寄存器中,并通过正常存储存储到内存中,这也是缓存一致的)。

https://en.wikipedia.org/wiki/Direct_memory_access#Cache_coherency
一些 Xeon 系统甚至可以将 DMA 进入 L3 缓存,从而避免主存延迟/带宽瓶颈(例如,用于千兆位网络)并节省电力。 https://en.wikipedia.org/wiki/Direct_memory_access#DDIO

没有缓存一致的旧系统确实必须小心避免在 DRAM 中的数据发生更改时出现陈旧的缓存命中。这是一个真正的问题,并且不仅限于开始一个新的过程。为不同文件的新mmap 重用刚刚释放的 (munmapped) 页面必须担心它。任何磁盘 I/O 都必须担心这一点,包括写入到磁盘:您需要将数据从缓存中同步到 DRAM,在 DRAM 中可以将其 DMA磁盘。 p>

这可能需要循环页面并运行类似clflush 的指令,或其他ISA 上的等效指令。 (我不知道操作系统在早于clflush 的 x86 CPU 上做了什么,如果有任何不是缓存一致的)你可能会在 Linux 内核的 doc 目录中找到一些关于它的信息。

这个 2002 年的 LWN article: DMA, small buffers, and cache incoherence 可能是相关的。那时,x86 已经被说成具有缓存一致性 DMA,所以也许 x86 一直都有这个。在 SSE 之前,我不知道 x86 如何可靠地使缓存无效,除了 wbinv 非常慢且系统范围内(使 所有 缓存行无效,而不仅仅是一页),对于性能原因。


无论哪种方式(一致与否),操作系统都不会浪费时间将零存储到它要从磁盘读取的页面。 对新进程的 BSS 以及它的任何页面进行归零分配给mmap(MAP_ANONYMOUS),而不是分配给它的代码/数据部分。

此外,您作为新进程执行的可执行文件可能已经在 RAM 中,在这种情况下,您只需设置新进程的页表即可。

【讨论】:

  • 我再次阅读了您的答案,但有一点我不明白:如果您在该数据中写入“0”,有效性位是否仍然打开?如果是:具有该物理地址的以下过程将进行错误命中并读取全零(这也是有效数据)。如果不是:在缓存 L2 和 L3 中,仍然会有前一个进程的数据占据物理内存的那个位置,而没有被归零。因此,无论哪种情况,它都不起作用。我错过了什么?
  • @isma:使用物理标记的缓存,永远不会出现错误命中。 CPU 总是读取最后写入内存的任何内容,因为缓存是一致的。 CPU 不关心存储是由内核还是由用户空间完成的。其他内核也无法从外层缓存中读取陈旧数据; MESI 一致性保证当存储提交到 L1d 缓存时,该核心对该行拥有独占所有权,外部缓存无效或跟踪该所有权。 TL:DR:缓存是一致的。将零写入物理地址后,它会读取为零。
  • @isma:但是,写零并不特别。下一个进程当然可以在最近归零的页面中获得缓存命中。物理标记(非虚拟标记)缓存的全部意义在于它不必在上下文切换时失效,包括结束一个进程(释放其页面)和启动另一个进程(回收这些页面)。 IDK 如果您正在考虑将0 存储在已经为零的数据上? What specifically marks an x86 cache line as dirty - any write, or is an explicit change required? - 静默存储尚未完成。
  • 彼得,我不明白的是:内核将页面归零,MESI 一致性协议使其他 L1d 缓存以及 L2 和 L3 缓存中的所有副本无效。但是在发生归零的 L1d 中,那条线仍然有效,对吧?因此,在以后的读取中,如果程序想要将一些数据放置在与该标签相对应的物理方向上,它将是一个命中,它会在数据中找到全零。这是不正确的!所以也许有一些关于 MESI 协议或归零的东西我仍然不明白。
  • @isma:任何不应该读为零的位置都需要由程序加载器写入,即从磁盘加载新程序的代码和数据,而不是清零页面。任何应该读为零的页面(新进程的 BSS)应该被内核清零。缓存不知道也不关心单独的进程,它只是确保读取看到最近写入的内容。由操作系统来确保最近写入的数据是正确的。这不是缓存问题,它只是一个操作系统正确性问题,在没有缓存的情况下也适用。
【解决方案2】:

当第一个进程终止时,它的所有物理内存页面都被操作系统“释放”。在几乎所有情况下,内核都会将这些新释放的页面的内容归零(这会使系统中这些物理地址的任何缓存副本无效)并“击落”相应的 TLB 条目(因此没有 TLB 保留来自先前虚拟地址到物理地址)。只有在每个 TLB 条目都被“击落”并且每个页面都被归零之后,内核才能将该页面添加到“空闲列表”中,此时它才有资格重复使用。

这种模式有很多变体,具体取决于硬件的功能和操作系统开发人员的偏好。我似乎记得在 MIPS 处理器的 SGI IRIX 操作系统中,TLB 击落是隐式完成的。 MIPS 硬件能够根据其编号(而不是其内容)使 TLB 条目无效。操作系统将每 10 毫秒击落一个 TLB 条目,然后为下一个间隔递增指针。在这 10 毫秒间隔中的 32 个(或 64 个?)之后,您可以保证系统中的所有 TLB 条目都已被刷新——因此任何超过 1 秒前释放的页面都可以保证没有陈旧的 TLB 条目,并且可以重新- 使用过(当然是在归零之后)。对于像 SGI Origin 2000 这样的可扩展共享内存系统来说,这似乎是一种合理的方法。

【讨论】:

  • 我认为现代 Linux 会进行惰性/最后一分钟归零,因此对于第一次写入时发生软页面错误的进程,新页面在 L1d 缓存中会很热。因此,无需跟踪空闲干净页面和空闲脏页面的单独列表。此外,如果第一次使用是内核只写的(例如,作为磁盘读取的 DMA 目标),那么早点将其归零没有任何好处。不过,关于 TLB 的有趣之处在于,对于可能在多个内核上运行的多线程进程的页面。
猜你喜欢
  • 2011-10-31
  • 1970-01-01
  • 1970-01-01
  • 2021-01-16
  • 2012-04-20
  • 2018-03-17
  • 2010-12-30
  • 2018-05-20
  • 2011-10-22
相关资源
最近更新 更多