1、数据一致性是一个重要的问题,它定义了不同的CPU、系统总线所有的master看到的是相同的一片内存。
2、因为cache的存在,以及编译器对某些C语言语句的优化,使得CPU对某个内存变量的修改不能立刻更新到内存,或者其他系统的master修改了内存变量,但是CPU仍然使用cache中的值或者寄存器中的值来代表变量,此时就发生了数据一致性的问题:不同的系统总线master对同一个变量看到不同的值(CPU也可以看做是系统总线的master)。
3、先看看编译器优化对数据一致性的影响:
有如下语句:
int i = 0;
while(1)
{
i++;
if(i > DELAY)
break;
}
假设上述代码片段用来实现延时,或者其他功能。此时编译器会将变量i的值读入CPU内部寄存器,初始化为0,在while循环体中,对i++的操作就是对寄存器的操作:
MOV R7,#+0
LDR R6,=0xFF
B ??main_0
??main_0:
ADD R7,R7,#+1
CMP R7,R6
BLT ??main_1
以上是ARM中对应的汇编语言。可以看到编译器使用R7来保存i,R6来保存DELAY(值为0xFF)常量,然后在while循环中,只是对存i变量的寄存器R7加1,并没有对i变量的内存操作。如果其他CPU,或者总线master依赖于变量i来控制一些功能,此时就会出错,因为i的最新值只是存在于CPU寄存器中。
针对这样的情况,我们可以使用volatile关键字告诉编译器,对i变量的读写,每次都要老老实实地从内存取,并且修改后,还要马上更新到内存:
volatile int i = 0;
while(1)
{
i++;
if(i > DELAY)
break;
}
对应的汇编语言是:
MOV R1,#+0
STR R1,[SP, #+0]
LDR R6,=0xFF
B ??main_0
??main_0:
LDR R0,[SP, #+0]
ADD R0,R0,#+1
STR R0,[SP, #+0]
LDR R0,[SP, #+0]
CMP R0,R6
BLT ??main_1
上面的汇编语言中,R6是常量DELAY的值0xFF,而SP是变量i的内存地址。由此可见,每次都是先SP指向的内存中(即i的地址)LDR到R0,R0++,然后再将R0的值更新到SP指向的内存中(即i的内存位置)。判断i是否大于DELAY时,也是先将i的值从SP指向的地址中LDR到R0,然后在和R6(DELAY的值)比较大小。
4、再看看cache对数据一致性的影响
如果开启了数据cache,那么CPU对内存的读写都要经过cache缓冲。读就是读cache,写也是写cache。
考虑一下情况:CPUwhile循环退出依赖于一个内存地址的值,并且这个内存地址的值由另外一个外设负责更新。如果开启数据cache,那么CPU总是从cache中读取数据,这时,cache中的数据和内存中的数据出现不一致,程序执行出现逻辑错误。注意,此时CPU是使用LDR访存指令来访问内存,但是仍然没有得到正确的内存数据。即使使用volatile关键字也无济于事,因为volatile是在指令级上影响C语言到汇编语言的关键字,但是CPU在访问内存时,仍然需要经过cache的缓冲。
在开数据cache的情况下,可以将特定的内存地址设置为不使用cache,以确保CPU访问的是内存。具体就是页表项的Cache属性。