嵌入式C语言关键字volatile以及cache对数据一致性的影响
1、数据一致性是一个重要的问题,它定义了不同的CPU、系统总线所有的master看到的是相同的一片内存。2、因为cache的存在,以及编译器对某些C语言语句的优化,使得CPU对某个内存变量的修改不能立刻更新到内存,或者其他系统的master修改了内存变量,但是CPU仍然使用cache中的值或者寄存器中的值来代表变量,此时就发生了数据一致性的问题:不同的系统总线master对同一个变量看到不同的值(
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属性。
5、Linux内核中关于ioremap与cache一致性的总结
(1)Linux-2.4.0——ioremap带cache,ioremap_nocache不带cache
extern inline void * ioremap (unsigned long offset, unsigned long size) /* 带cache */
{
return __ioremap(offset, size, 0);
}
extern inline void * ioremap_nocache (unsigned long offset, unsigned long size) /* 不带cache */
{
return __ioremap(offset, size, _PAGE_PCD); //该标记会写到页表项,MMU在执行地址映射的时候通过这个标记进行cache缓存控制,打上该标记后,将绕过cache从物理内存读/写数据
}
(1)Linux-2.6.0以后...——ioremap_nocache废弃,ioremap变为不带cache,提供带cache的ioremap_cache接口
内核废弃了ioremap_nocache,但是为了兼容性保留了接口(与ioremap一模一样)。默认ioremap不带cache的,同时内核提供了带cache的ioremap_cache
#define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE)
#define ioremap_nocache(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE)
#define ioremap_cache(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE_CACHED)
#define ioremap_wc(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE_WC)
#define iounmap __arm_iounmap
———————————————————————————————————————————————————————
版权声明:本文为CSDN博主「a747lulu747」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/a747lulu747/article/details/12423031
其中第5点是个人总结;
———————————————————————————————————————————————————————
更多推荐
所有评论(0)