现代x86/amd64芯片上的L1、L2、L3 CPU缓存如何关闭?

16
每个现代高性能的x86/x86_64架构CPU都有一些数据缓存层次结构:L1、L2,有时还有L3(在非常罕见的情况下还有L4),从/到主RAM加载的数据会被缓存在其中一些缓存中。
有时程序员可能希望某些数据不要被缓存在某些或所有缓存级别中(例如,当想要memset 16 GB的RAM并仍然保留一些数据在缓存中时):这里有一些非临时(NT)指令可以实现,如MOVNTDQA(https://dev59.com/83VD5IYBdhLWcg3wQZQg#37092 http://lwn.net/Articles/255364/)。
但是,是否有一种编程方式(适用于像P3、P4、Core、Core i*等某些AMD或Intel CPU系列),可以完全(但暂时)关闭某些或所有级别的缓存,以更改每个内存访问指令(全局或对某些应用程序/ RAM区域)如何使用内存层次结构?例如:关闭L1,关闭L1和L2?或将每个内存访问类型更改为“非缓存”UC(CR0的CD + NW位?SDM vol3a页423 424425和“第三级高速缓存禁用标志,IA32_MISC_ENABLE MSR的第6位(仅适用于基于Intel NetBurst微体系结构的处理器)-允许独立于L1和L2缓存禁用和启用L3缓存”)。
我认为这样的操作将有助于保护数据免受缓存侧信道攻击/泄漏,例如窃取AES密钥、隐秘缓存通道、Meltdown/Spectre。尽管这种禁用会带来巨大的性能代价。

PS:我记得很多年前在一些技术新闻网站上发布过这样的程序,但现在找不到了。它只是一个Windows exe程序,可以将一些神奇的值写入MSR,并使每个在其后运行的Windows程序变得非常缓慢。缓存被关闭直到重新启动或以“撤消”选项启动程序。


你好!请查看Intel SDM vol. 3a的“禁用和启用L3缓存”(以及周围)和“MTRR”部分 https://software.intel.com/en-us/articles/intel-sdm - https://xem.github.io/minix86/manual/intel-x86-and-64-manual-vol3/o_fe12b1e2a880e0ce-429.html https://xem.github.io/minix86/manual/intel-x86-and-64-manual-vol3/o_fe12b1e2a880e0ce-428.html “第三级缓存禁用标志(IA32_MISC_ENABLE MSR的第6位)允许独立于L1和L2缓存禁用和启用L3缓存”,以及CR0寄存器的CD标志下的IA32_MISC_ENABLE,第424页“表11-5。高速缓存操作模式”。 - osgx
此外:https://www.linuxquestions.org/questions/linux-kernel-70/disabling-cpu-caches-936077/和memtest的`cache_on`/`cache_off`函数:https://github.com/vathpela/memtest86-/blob/master/test.h#L206;https://software.intel.com/en-us/forums/software-tuning-performance-optimization-platform-monitoring/topic/278571“*缓存可能不会被使用,但它们没有被禁用*”和https://software.intel.com/en-us/forums/software-tuning-performance-optimization-platform-monitoring/topic/737627“*CR0.CD的范围是“核心”*”。另外,对于部分禁用:PCD“页面级缓存禁用(cr3的第4位)”。 - osgx
1个回答

19

英特尔的手册3A,第11.5.3节提供了一个算法来全局禁用缓存:

11.5.3 防止缓存

在启用并填充缓存之后,要禁用L1、L2和L3缓存,请执行以下步骤:

  1. 进入无填充缓存模式。(将控制寄存器CR0中的CD标志设置为1且NW标志设置为0)。
  2. 使用WBINVD指令刷新所有缓存。
  3. 禁用MTRRs并将默认内存类型设置为不带缓存,或将所有MTRRs设置为不带缓存的内存类型(有关TYPE字段和E标志的讨论,请参见第11.11.2.1节,“IA32_MTRR_DEF_TYPE MSR”的讨论)。

必须在设置CD标志后刷新缓存(步骤2),以确保系统内存一致性。如果不刷新缓存,则读取时会发生缓存命中,并且数据将从有效的缓存行中读取。

上述三个单独步骤的意图分别解决了三个不同的要求:(i)停止新数据替换缓存中的现有数据(ii)确保缓存中已有的数据被逐出到内存,(iii)确保随后的内存引用遵循UC内存类型语义。不同处理器实现的缓存控制硬件可能允许对这三个要求进行某些软件实现的变化。见下面的注释。

注释 在控制寄存器CR0中设置CD标志会修改处理器的缓存行为,如表11-5所示,但仅设置CD标志可能不足以跨所有处理器系列强制使所有物理内存的有效内存类型为UC,也不能强制执行严格的内存顺序,由于不同处理器系列之间的硬件实现差异。要在所有物理内存上强制使用UC内存类型和严格的内存顺序,可以将所有物理内存的MTRRs编程为UC内存类型或禁用所有MTRR。

对于Pentium 4和Intel Xeon处理器,在执行上述步骤序列之后,包含WBINVD指令结束前和MTRRS禁用之前的代码的缓存行实际上可能仍保留在缓存层次结构中。为了完全从缓存中删除代码,必须在禁用MTRRs之后执行第二个WBINVD指令。

这是一段冗长的引用,但归结为以下代码:

;Step 1 - Enter no-fill mode
mov eax, cr0
or eax, 1<<30        ; Set bit CD
and eax, ~(1<<29)    ; Clear bit NW
mov cr0, eax

;Step 2 - Invalidate all the caches
wbinvd

;All memory accesses happen from/to memory now, but UC memory ordering may not be enforced still.  

;For Atom processors, we are done, UC semantic is automatically enforced.

xor eax, eax
xor edx, edx
mov ecx, IA32_MTRR_DEF_TYPE    ;MSR number is 2FFH
wrmsr

;P4 only, remove this code from the L1I
wbinvd
大部分代码不可从用户模式执行。

AMD手册2在第7.6.2节中提供了一个类似的算法。

7.6.2缓存控制机制
AMD64架构提供了许多控制内存可缓存性的机制。这些机制在以下各节中描述。

缓存禁用。CR0寄存器的第30位是缓存禁用位,CR0.CD。当CR0.CD被设置为1时,禁用缓存;当CR0.CD清除为0时,则启用缓存。禁用缓存时,读取和写入将直接访问主存。

即使在缓存仍然保存有效数据(或指令)时,软件也可以禁用缓存。如果CR0.CD=1时,读取或写入命中L1数据缓存或L2缓存,处理器会执行以下操作:

  1. 如果缓存行处于修改或拥有状态,则将缓存行写回。
  2. 使缓存行失效。
  3. 执行非可缓存的主存访问以读取或写入数据。

如果指令获取命中L1指令缓存时,CR0.CD=1,某些处理器型号可能会读取缓存指令而不是访问主存。当CR0.CD=1时,L2和L3缓存的确切行为因型号而异,并且可能对不同类型的内存访问有所不同。

当CR0.CD=1时,处理器还会响应缓存探测。命中缓存的探测会导致处理器执行步骤1。仅当探测代表内存写入或排他读取时,才执行步骤2(缓存线失效)。

非写穿禁用。CR0寄存器的第29位是非写穿禁用位,CR0.NW。在早期的x86处理器中,CR0.NW用于控制缓存写穿行为,而CR0.NW和CR0.CD的组合决定了缓存的操作模式。

[...]

在AMD64架构的实现中,CR0.NW不用于限定由CR0.CD建立的缓存操作模式。

这转换成以下代码(与Intel的代码非常相似):

;Step 1 - Disable the caches
mov eax, cr0
or eax, 1<<30
mov cr0, eax

;For some models we need to invalidated the L1I
wbinvd

;Step 2 - Disable speculative accesses
xor eax, eax
xor edx, edx
mov ecx, MTRRdefType  ;MSR number is 2FFH
wrmsr

缓存也可以在以下情况下进行选择性禁用:

  • 页面级别,使用属性位PCD(页面缓存禁用)[仅适用于Pentium Pro和Pentium II]。
    当两者均未设置时,相关的MTTR将被使用。如果PCD被设置,则不进行缓存。
  • 页面级别,使用PAT(页面属性表)机制。
    通过将IA32_PAT填充为缓存类型并使用PAT、PCD、PWT位作为3位索引,可以选择六种缓存类型之一(UC-、UC、WC、WT、WP、WB)。
  • 使用MTTR(固定或可变)。
    将缓存类型设置为UC或UC-以针对特定的物理区域。

其中,只有页面属性选项可以暴露给用户模式程序(例如请参阅此处)。


那么,PCD位和MTTR禁用所有级别的缓存吗?选择性禁用呢?IA32_MISC_ENABLE MSR的第6位是否仅禁用L3并保持L1和L2在线,有文档记录吗?是否有可用的Linux内核模块源代码来测试CR0.CD禁用? - osgx
@osgx IA32_MISC_ENABLE 的第6位应该只在Netburst架构(Pentium 4和当时的Xeons)中存在。然而,最近的Xeon拥有CAT(Cache Allocation Technology),在第17.18章节中描述了它可以将LLC的块分配给核心(包括不分配任何块)。MTTRs禁用所有缓存(处理器甚至不响应嗅探)。由于页面别名的原因,PCD位也是如此,我不确定命中是否会直接到达内存(我相信会,任何命中的行应该在任何访问上被无效化,并且在从启用缓存的别名页面访问时最终重新填充) - Margaret Bloom
@osgx 我不知道有任何内核模块,但是这里有一个示例模块链接。如果您不介意编译的话,我可以尝试将其转换为一个禁用缓存的模块。 - Margaret Bloom
@MargaretBloom 你好。我尝试运行你的示例,但出现了错误“error: symbol `IA32_MTRR_DEF_TYPE' undefined”。我在Intel x86_64上进行了测试。 - Tigran84
1
@Tigran84 你需要定义IA32_MTRR_DEF_TYPE,我没有这样做是因为代码旨在成为片段。注释还告诉了MSR号码,所以你可以在代码顶部添加%define IA32_MTRR_DEF_TYPE 0x2ff(如果使用NASM,其他汇编器有不同的语法),或者将mov ecx, IA32_MTRR_DEF_TYPE替换为mov ecx, 0x2ff。对于AMD代码也是一样的。 - Margaret Bloom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接