【问题标题】:C volatile variables and Cache MemoryC volatile 变量和高速缓存
【发布时间】:2011-12-13 21:41:40
【问题描述】:

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

我的理解是,

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

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

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

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

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

谢谢, 微内核:)

【问题讨论】:

    标签: c computer-science volatile computer-architecture memorycache


    【解决方案1】:

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

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

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

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

    希望对您有所帮助。如果我遗漏了什么,请告诉我,我会扩大答案。

    【讨论】:

    • volatile 的目的,在使用好的编译器时,应该是确保生成的代码让处理器知道在某个点之前需要编写的所有内容,并且不会询问处理器读取信息直到之后。程序员可能还需要使用内在函数或其他方法来强制刷新硬件缓存,但如果编译器以硬件一无所知的方式对事物进行寄存器缓存,那么强制刷新硬件缓存将毫无用处。
    【解决方案2】:

    根据您的问题,您有一个误解。
    Volatile 关键字与您描述的缓存无关。

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

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

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

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

    【讨论】:

    • 那么,如果我已经采取了一个变量被其他线程或驱动程序从输入设备读取更新的情况,那么有什么保证我正在读取正确的值而不是缓存的东西?如何在代码中避免这种情况?
    • 如果您使用volatile,可以保证您将始终从另一个线程读取在内存中完成的最新更新。但我感觉您的关注更多是在操作系统级别,即缓存与内存同步
    • @Cratylus 如果您使用线程,则“最新”、“过去”......在运行在差异核心上的线程之间没有明确定义。
    【解决方案3】:

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

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

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

    【讨论】:

    • 那么,取决于平台?因此,缓存不受缓存硬件控制? (如果硬件完全管理缓存,那么它不会检查标志 PAGE_NOCACHE 对吗?)
    • @Microkernel:它由硬件管理的。但是操作系统告诉硬件该做什么(毕竟硬件不知道操作系统想要如何管理内存),而你要求操作系统做你想做的事。 所有这些信息都存储在——猜猜在哪里? -- 内存本身。 不过,这是一个被动的过程 -- 操作系统只会在出现问题时进行干预(例如页面错误)。除此之外,硬件只是继续执行操作系统要求它执行的操作,无需操作系统干预。
    • 嗯,好吧...好像我的理解在某个地方是错误的,我一直认为 CPU Cache 对除了 Cache 硬件之外的所有人都是透明的!为了使我的概念正确,我必须阅读任何参考资料? !非常感谢您的澄清:)
    • @Microkernel:当然! :) 基本上,操作系统将其所有内存管理信息存储在内存中的“页表”中,并告诉 CPU 在哪里查找信息。然后,CPU 管理一切,并在无法决定要做什么时向操作系统寻求“帮助”。您可以阅读有关分页 here 和有关缓存 here 的信息;如果您还有任何问题,请告诉我。 (这就是为什么他们说操作系统位于硬件和软件之间——确实如此!)
    【解决方案4】:

    Wikipedia has a pretty good article about MTRR (Memory Type Range Registers) 适用于 x86 系列 CPU。

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

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

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

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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

      • 仅当机器只有一个 CPU 时。
      • @JeremyP,我认为这里的问题超出了对共享内存的并发访问范围。如果你还有这个,是的,一切都会变得更加复杂。然后,您必须应用适当的工具来确保数据一致性。但是,这是一个更普遍的问题,从缓存的角度来看可能也不是正确的观点。
      • 我认为这并没有超出并发访问内存的范围。问题的前提是并发访问内存,否则,正如您所指出的,缓存是透明的。
      • 机器不需要多于一个 CPU。内存映射的设备控制寄存器可以产生相同的效果(对于硬核 MCU,设计人员可能会注意不要缓存该地址空间,对于 FPGA/PLD 上的软核,不一定)。见altera.com/ja_JP/pdfs/literature/hb/nios2/n2sw_nii52007.pdf第4页
      • @JeremyP "仅当机器只有一个 CPU 时" 这并不总是错误的,但极具误导性。它应该是:仅当机器没有多个不用于线程支持的处理单元时。 如果 CPU 设计为支持线程,那么它是有保证的。
      【解决方案6】:

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

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

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

      【讨论】:

      • 我不同意这篇文章。 volatile 关键字只是阻止 C 编译器对变量进行某些优化。它对缓存做任何事情。一些编译器可能让你能够混淆这个关键字的含义(ARC 编译器就是其中之一),但对于大多数编译器来说,情况并非如此。
      【解决方案7】:

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

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

      【讨论】:

      • 代码按钮的存在是有原因的。请不要滥用格式。
      • 哪个编译器支持_Uncached关键字?谷歌搜索“_Uncached”会给出您的答案作为第一个结果。
      猜你喜欢
      • 2017-11-25
      • 1970-01-01
      • 2017-09-26
      • 1970-01-01
      • 2015-08-14
      • 1970-01-01
      相关资源
      最近更新 更多