本帖最后由 blake326 于 2013-03-07 00:12 编辑
http://bbs.chinaunix.net/forum.p ... =4070325&extra=
http://bbs.chinaunix.net/forum.p ... ;page=6#pid23786513
参考上面两个帖子。纯属个人总结理解,具体实现应该更复杂或者以此为基础。
先说cache 一致性的理解。
假设有cpu0, cpu1。一个虚拟地址addr。
则cpu0, cpu1都有一个addr对应的cache line, 状态有四种:有效E, 修改M,共享S, 无效I。I实际上就是说该addr在cache中没有缓存的意思。根据这四种状态,组合分析一下。
cpu0,cpu1的cache状态一样:
都是I状态:
这种最简单,cpu0读addr的话,则分配一个cache line并且从ddr把addr的值读取过来,状态变成E。cpu0写的话,状态会变成M。
都是S状态:
也很简单,读的话直接从cache读出来。写的话复杂一点,cpu0写addr,修改本地cache,状态变成M。并且发送一个inv消息给cpu1通知cpu1失效响应的cache line。
都是E,M的状态是不存在的。
cpu0,cpu1的cache状态不一样:
cpu0 I, cpu1 E:
cpu0 读addr,发现cpu1有对应的E cache,新建一个本地的cache line将cpu1的cache line拷贝过来,并且设置大家的状态为S。
cpu0 写addr,发现cpu1有对应的E cache,发送一个inv消息告诉cpu1,然后本地新建一个cache line将ddr读进来,之后修改本地cache line,并且设置状态M。
cpu1 读addr,直接读cache。
cpu1 写addr,直接写cache,设置状态为M。
cpu0 I, cpu1 M:
cpu0 读addr,发现cpu1有对应的M cache,发送一个flush消息告诉cpu1并且等待cpu1刷新cache到ddr,然后新建一个本地cache line将ddr从内存读进来,并且设置状态为E。
cpu0 写addr,发现cpu1有对应的M cache,发送一个flush消息告诉cpu1并且等待cpu1刷新cache到ddr,然后新建一个本地cache line将ddr从内存读进来并且写,并且设置状态为M。
cpu1 读addr,直接读。
cpu1 写addr,直接写。
内存屏障,一个经典的场景,插入链表一个节点。
cpu0
new->next = next;
wmb();
prev->next = new;
cpu1
读 prev->next
rmb();
读 new->next
如果没有内存屏障,cpu1读到最新的prev->next的时候,可能还没有读到最新的new->next。内核就要挂了。
换一种舒服的例子:
cpu0
a = 1;
wmb();
b = 2;
cpu1
读 b;
rmb();
读 a;
通过内存屏障能够保证cpu1假设读取到了b=2,那么cpu1读取的a肯定等于1。
看一看实现:
假设现在a和b在不同的cache line,并且不管在哪个cpu,他们的状态都是S的。(其他状态可以一样的分析)
cpu0三条指令,通过wmb()保证 先发送inv a,在发送inv b。或者说是,cpu1先收到inv a,后收到inv b的消息。
cpu1三条指令,rmb()保证在执行后面的指令之前,rmb前面所有的inv 指令都必须执行完成。假设读b的时候,inv b已经收到并且已经执行,则b获取到了最新的值。那么在执行读a的指令之前,一定会吧inv a执行了,因为inv a在inv 本就收到了(这个是cpu0的wmb保证的)
所以wmb,rmb都是缺一不可的。
补充改一下L2 cache,arm架构经常会有l2 cache, 不同与L1cahce, L2 cache是smp共享的一个cache。上面说的cache刷新到ddr,从ddr读取到cache,其实都是经过这个L2 cache的,比如cache刷新ddr,如果l2cache也有对应的cache的话则这个cache知会刷新到l2 cache, l2 cache会等待适当的时机或者主动调用api被刷新到真正的ddr。读的话也类似。
一般来说,L2 cache对软件来说是很透明的,除了启动的时候要初始化一下,并且需要将cache刷新真正的ddr的时候可以调用响应的操作方法来刷新。比如dma操作之前,可能需要clean或者inv L2 cache。