39

缓存是由缓存硬件对处理器透明控制的,所以如果我们在 C 程序中使用 volatile 变量,如何保证我的程序每次都从指定的实际内存地址读取数据而不是缓存。

我的理解是,

  1. Volatile 关键字告诉编译器不应该优化变量引用,而应该按照代码中的程序来读取。

  2. 缓存由缓存硬件透明地控制,因此当处理器发出地址时,它不知道数据是来自缓存还是来自内存。

因此,如果我需要每次都必须读取内存地址,我如何确保它不是从缓存中引用,而是从所需地址引用?

不知何故,这两个概念并不能很好地结合在一起。请澄清它是如何完成的。

(假设我们在缓存中有回写策略(如果需要分析问题))

谢谢你,微内核:)

4

7 回答 7

36

固件开发人员在这里。这是嵌入式编程中的一个标准问题,并且困扰了许多(甚至非常有经验的)开发人员。

我的假设是您正在尝试访问硬件寄存器,并且该寄存器值会随时间而变化(无论是中断状态、定时器、GPIO 指示等)。

关键字只是解决方案的volatile一部分,在许多情况下可能不是必需的。这会导致变量在每次使用时都从内存中重新读取(而不是被编译器优化出来或存储在处理器寄存器中多次使用),但是正在读取的“内存”是否是实际的硬件寄存器与缓存位置相比,您的代码是未知的,并且不受volatile关键字的影响。如果您的函数只读取一次寄存器,那么您可能可以停止volatile,但作为一般规则,我建议大多数硬件寄存器应定义为volatile.

更大的问题是缓存和缓存一致性。这里最简单的方法是确保您的寄存器位于未缓存的地址空间中。这意味着每次访问寄存器时,您都可以保证读取/写入实际的硬件寄存器而不是缓存。一种更复杂但性能可能更好的方法是使用缓存地址空间,并让您的代码针对此类特定情况手动强制缓存更新。对于这两种方法,如何实现都取决于架构,超出了问题的范围。它可能涉及 MTRR(对于 x86)、MMU、页表修改等。

希望有帮助。如果我错过了什么,请告诉我,我会扩展我的答案。

于 2011-10-24T15:27:42.817 回答
9

从你的问题来看,你有一个误解。
Volatile关键字与您描述的缓存无关。

volatile为变量指定关键字时,它会提示编译器不要进行某些优化,因为此变量可能会从程序的其他部分意外更改。

这里的意思是,编译器不应该重用已经加载到寄存器中的值,而是再次访问内存,因为不保证寄存器中的值与存储在内存中的值相同。

其余有关高速缓存的内容与程序员没有直接关系。

我的意思是 CPU 的任何高速缓存与 RAM 的同步是一个完全不同的主题。

于 2011-10-24T07:26:58.457 回答
7

我的建议是将页面标记为虚拟内存管理器未缓存的页面。
在 Windows 中,这是通过PAGE_NOCACHE调用时的设置来完成的VirtualProtect

出于某种不同的目的,SSE 2 指令具有_mm_stream_xyz防止缓存污染的指令,尽管我认为它们不适用于您的情况。

无论哪种情况,在 C 中都没有可移植的方式来做你想做的事情。您必须使用操作系统功能。

于 2011-10-24T06:59:48.453 回答
2

维基百科有一篇关于 MTRR(内存类型范围寄存器)的非常好的文章,适用于 x86 系列 CPU。

总而言之,从 Pentium Pro 开始,英特尔(和 AMD 复制)有这些 MTR 寄存器,可以在内存范围上设置未缓存、直写、写组合、写保护或回写属性。

从 Pentium III 开始,但据我所知,仅对 64 位处理器真正有用,它们尊重 MTRR,但它们可以被页面属性表覆盖,这让 CPU 为每个内存页设置内存类型。

我所知道的 MTRR 的一个主要用途是图形 RAM。将其标记为写入组合会更有效。这使缓存可以存储写入,并放宽所有内存写入顺序规则,以允许对图形卡进行非常高速的突发写入。

但出于您的目的,您可能需要 MTRR 或未缓存或直写的 PAT 设置。

于 2011-10-24T07:27:45.430 回答
0

使用 _Uncached 关键字可能有助于嵌入式操作系统,例如 MQX

#define MEM_READ(addr)       (*((volatile _Uncached unsigned int *)(addr)))
#define MEM_WRITE(addr,data) (*((volatile _Uncached unsigned int *)(addr)) = data)
于 2012-12-17T21:59:28.267 回答
0

正如您所说,缓存对程序员是透明的。如果您通过对象的地址访问对象,系统会保证您始终看到上次写入的值。如果缓存中有一个过时的值,您可能会招致的“唯一”事情是运行时惩罚。

于 2011-10-24T07:36:35.507 回答
0

volatile确保每次需要时都读取数据,而无需担心 CPU 和内存之间的任何缓存。但是如果您需要从内存中读取实际数据而不是缓存数据,您有两种选择:

  • 制作一个未缓存所述数据的板。如果您寻址某些 I/O 设备,可能已经是这种情况,
  • 使用绕过缓存的特定 CPU 指令。这在您需要清理内存以激活可能的 SEU 错误时使用。

第二个选项的细节取决于操作系统和/或 CPU。

于 2011-10-24T07:52:33.210 回答