C易失性变量和缓存内存



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

我的理解是,

  1. Volatile关键字告诉编译器变量引用不应该被优化,应该在代码中编程时读取。

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

所以,如果我有一个要求,必须读取内存地址每次需要,我怎么能确保它不是从缓存引用,而是从所需的地址?

不知为什么,这两个概念不能很好地结合在一起。请说明是怎么做的。

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

谢谢你,微内核:)

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

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

volatile关键字只是解决方案的一部分,在许多情况下可能不是必需的。这导致每次使用变量时都要从内存中重新读取变量(而不是由编译器优化出来或跨多个用途存储在处理器寄存器中),但是对于代码来说,读取的"内存"是实际的硬件寄存器还是缓存位置是未知的,并且不受volatile关键字的影响。如果你的函数只读取寄存器一次,那么你可能会离开volatile,但作为一般规则,我建议大多数硬件寄存器应该定义为volatile

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

希望有帮助。如果我遗漏了什么,让我知道,我会扩展我的答案。

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

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

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

关于缓存的其余部分与程序员没有直接关系。

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

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

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

在任何一种情况下,都没有可移植的方法在C中做你想做的事情;

Wikipedia有一篇关于MTRR(内存类型范围寄存器)的很好的文章,它适用于x86系列的cpu。

总结一下,从奔腾Pro开始,英特尔(和AMD复制)有这些MTR寄存器,可以在内存范围内设置未缓存,透写,组合写,写保护或回写属性。

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

我所知道的mtrr的一个主要用途是图形RAM。将其标记为write- combination会更有效。这可以让缓存存储写操作,并放宽所有内存写顺序规则,以允许非常高速的突发写入显卡。

但是出于您的目的,您可能需要MTRR或PAT设置为非缓存或透写。

正如您所说,缓存对程序员是透明的。如果您通过地址访问对象,系统保证始终看到最后写入的值。如果缓存中存在过时的值,那么"唯一"可能导致的是运行时损失。

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

  • 创建一个不缓存所述数据的板。如果您对某些I/O设备(
  • )进行寻址,可能已经是这种情况了。
  • 使用绕过缓存的特定CPU指令。当您需要清除内存以激活可能的SEU错误时使用。

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

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

#define MEM_READ(addr)       (*((volatile _Uncached unsigned int *)(addr)))
#define MEM_WRITE(addr,data) (*((volatile _Uncached unsigned int *)(addr)) = data)

最新更新