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点是个人总结;

———————————————————————————————————————————————————————

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐