缓存一致性(缓存物理标记的特殊情况)

cache coherency (particular case of cache physically tagged)

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

编辑:假设是:一个进程完成,另一个进程从磁盘传送到内存,再到内存的同一页,再到 运行。我的问题是:采取什么措施来防止这方面出现问题?我知道,在第二个进程被存入内存之前,页面被清零了。所以现在在缓存中我们有对应于该页面的零。但是页面有第二个进程的数据。这就是我所理解的,但可能是错误的。

Peter Cordes 的回答很完美!

But the data remaining in cache is from the previous process

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

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

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

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

I understand that the OS zero a physical page but this is in main memory, but I'm talking about the residual data in cache memory.

我认为 this 是您混淆的根源:这种归零是通过 CPU.[=72 执行的普通存储指令进行的=] CPU 上的 OS 运行 并将通过遍历存储零的字节(或字)将页面归零。这些存储是普通的可缓存存储,与 cache/memory 层次结构顶部的任何其他写入相同。

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


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


正在为新进程加载新的 code/data

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

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

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

这可能需要遍历一个页面并 运行 执行一条指令,如 clflush,或其他 ISA 上的等效指令。 (我不知道 OSes 在早于 clflush 的 x86 CPUs 上做了什么,如果有任何缓存不一致的话)你可能会在Linux 内核的文档目录。

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


无论哪种方式(一致或不一致),OS 都不会浪费时间将零存储到即将从磁盘读取的页面。 清零已完成一个新进程的 BSS,以及它分配的任何页面 mmap(MAP_ANONYMOUS),而不是它的 code/data 部分。

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

当第一个进程终止时,它的所有物理内存页都被 OS "freed"。在几乎所有情况下,内核都会将这些新释放的页面的内容清零(这会使系统中任何地方的这些物理地址的任何缓存副本无效)和 "shoots down" 相应的 TLB 条目(因此没有 TLB 保留来自先前的映射虚拟地址到物理地址)。只有在每个 TLB 条目 "shot down" 并且每个页面都被清零之后,内核才能将该页面添加到 "free list",此时它才有资格重新使用。

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