缓存由缓存硬件透明地控制到处理器,因此如果我们在C程序中使用volatile变量,我的程序如何保证每次从指定的实际内存地址而不是缓存中读取数据。
我的理解是,
Volatile关键字告诉编译器不应优化变量引用,并应按代码中的编程读取。
缓存由缓存硬件透明地控制,因此当处理器发出地址时,它不知道数据是来自缓存还是来自内存。
所以,如果我需要每次都需要读取一个内存地址,我怎样才能确保它不是从缓存引用而是从所需的地址引用?
有些如何,这两个概念并不合适。请说明它是如何完成的。
(想象一下我们在缓存中有回写策略(如果需要分析问题))
谢谢你, 微内核:)
答案 0 :(得分:31)
此处是固件开发人员。这是嵌入式编程中的一个标准问题,也是许多(甚至非常有经验的)开发人员绊倒的问题。
我的假设是您正在尝试访问硬件寄存器,并且该寄存器值可能随时间而变化(无论是中断状态,定时器,GPIO指示等)。
volatile
关键字只是解决方案的一部分,在许多情况下可能没有必要。这会导致变量在每次使用时从内存重新读取(而不是由编译器优化或跨多个用途存储在处理器寄存器中),但是否正在读取的“内存”是一个实际的硬件寄存器,而缓存的位置对于您的代码是未知的,并且不受volatile
关键字的影响。如果您的函数只读取寄存器一次,那么您可以放弃volatile
,但作为一般规则,我建议大多数硬件寄存器应定义为volatile
。
更大的问题是缓存和缓存一致性。这里最简单的方法是确保您的寄存器位于未缓存的地址空间中。这意味着每次访问寄存器时,都可以保证读/写实际的硬件寄存器而不是高速缓冲存储器。更复杂但可能性能更好的方法是使用缓存的地址空间,并让您的代码手动强制缓存更新,以适应这种特定情况。对于这两种方法,如何实现这一点取决于体系结构,超出了问题的范围。它可能涉及MTRR(用于x86),MMU,页表修改等。
希望有所帮助。如果我错过了什么,请告诉我,我会扩大答案。
答案 1 :(得分:7)
我的建议是将页面标记为虚拟内存管理器未缓存
在Windows中,这是通过在调用PAGE_NOCACHE
时设置VirtualProtect
来完成的。
出于某种不同的目的,SSE 2 instructions有_mm_stream_xyz
指令以防止缓存污染,但我不认为它们适用于您的情况。
在任何一种情况下,都没有便携式方式在C中做你想做的事情;你必须使用操作系统功能。
答案 2 :(得分:7)
从你的问题来看,你有一种误解。
如您所述,Volatile
关键字与缓存无关。
当为变量指定关键字volatile
时,它会提示编译器不要进行某些优化,因为此变量可能会意外地从程序的其他部分更改。
这里的意思是编译器不应该重用已经加载到寄存器中的值 ,而是再次访问内存,因为寄存器中的值不能保证与值相同存储在内存中。
有关高速缓冲存储器的其余部分与程序员没有直接关系。
我的意思是CPU的任何高速缓存与RAM的同步是一个完全不同的主题。
答案 3 :(得分:2)
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设置为未缓存或直写。
答案 4 :(得分:1)
使用_Uncached关键字可能有助于嵌入式操作系统,如MQX
#define MEM_READ(addr) (*((volatile _Uncached unsigned int *)(addr)))
#define MEM_WRITE(addr,data) (*((volatile _Uncached unsigned int *)(addr)) = data)
答案 5 :(得分:0)
正如您所说,缓存对程序员来说是透明的。如果您通过其地址访问对象,系统可以保证您始终可以看到上次写入的值。如果缓存中存在过时的值,那么“唯一”的事情就是运行时损失。
答案 6 :(得分:0)
volatile
确保每次需要时都会读取数据,而不必担心CPU和内存之间的任何缓存。但是,如果您需要从内存中读取实际数据而不是缓存数据,则有两种选择:
第二个选项的细节取决于操作系统和/或CPU。