Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1495839
  • 博文数量: 267
  • 博客积分: 3010
  • 博客等级: 少校
  • 技术积分: 3089
  • 用 户 组: 普通用户
  • 注册时间: 2011-06-05 17:09
个人简介

尊天命,尽人事

文章分类

全部博文(267)

文章存档

2017年(6)

2015年(4)

2014年(27)

2013年(52)

2012年(59)

2011年(120)

分类: LINUX

2011-12-12 20:05:09

内核中定义的内存屏障原语有: 

#define barrier() __asm__ __volatile__("": : :"memory") 
#define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2) 
#define rmb() alternative("lock; addl $0,0(%%esp)", "lfence", X86_FEATURE_XMM2) 

#ifdef CONFIG_SMP 
#define smp_mb() mb() 
#define smp_rmb() rmb() 
#define smp_wmb() wmb() 
#define smp_read_barrier_depends() read_barrier_depends() 
#define set_mb(var, value) do { (void) xchg(&var, value); } while (0) 
#else 
#define smp_mb() barrier() 
#define smp_rmb() barrier() 
#define smp_wmb() barrier() 
#define smp_read_barrier_depends() do { } while(0) 
#define set_mb(var, value) do { var = value; barrier(); } while (0) 
#endif 


1). smp_xxx()和xxx()的区别 

为了给其它CPU也提供相关的barrier宏。 例如x86的rmb()是用了lfence指令,但其它CPU不能用这个指令。 


2). 关于barrier()宏,jkl大师是这么说的: 

CPU越过内存屏障后,将刷新自己对存储器的缓冲状态。这条语句实际上不生成任何代码,但可使gcc在 
barrier()之后刷新寄存器对变量的分配。 

也就是说,barrier()宏只约束gcc编译器,不约束运行时的CPU行为。 举例: 

1 int a = 5, b = 6; 
2 barrier(); 
3 a = b; 

在line 3,GCC不会用存放b的寄存器给a赋值,而是invalidate b的Cache line,重新读内存中的b值,赋值给a。 


3). mb() vs. rmb() vs. wmb() 

rmb()不允许读操作穿过内存屏障;wmb()不允许写操作穿过屏障;而mb()二者都不允许。 

看IA32上wmb()的定义: 
#ifdef CONFIG_X86_OOSTORE 
#define wmb() alternative("lock;addl $0,0(%%esp)", "sfence", X86_FEATURE_XMM); 
#else 
#define wmb() __asm__ __volatile__ ("": : :"memory"); 
#endif 

Intel和AMD都没有在IA32 CPU中实现乱序写(Out-Of-Order Store),所以wmb()定义为空操作,不约束CPU行为;但 
有些IA32 CPU厂商实现了OOO Store,所以就有了使用sfence的那个wmb()实现。 


4). 内存屏障的体系结构语义 

4.1) 只有一个主体(CPU或DMA控制器)访问内存时,无论如何也不需要barrier;但如果有两个或更多主体访问内存,且 
其中有一个在观测另一个,就需要barrier了。 

4.2) IA32 CPU调用有lock前缀的指令,或者如xchg这样的指令,会导致其它的CPU也触发一定的动作来同步自己的Cache。 
CPU的#lock引脚链接到北桥芯片(North Bridge)的#lock引脚,当带lock前缀的执行执行时,北桥芯片会拉起#lock
电平,从而锁住总线,直到该指令执行完毕再放开。 而总线加锁会自动invalidate所有CPU对 _该指令涉及的内存_ 
的Cache,因此barrier就能保证所有CPU的Cache一致性。 

4.3) 接着解释。 
lock前缀(或cpuid、xchg等指令)使得本CPU的Cache写入了内存,该写入动作也会引起别的CPU invalidate其Cache。 
IA32在每个CPU内部实现了Snoopying(BUS-Watching)技术,监视着总线上是否发生了写内存操作(由某个CPU或DMA控 
制器发出的),只要发生了,就invalidate相关的Cache line。 因此,只要lock前缀导致本CPU写内存,就必将导致
所有CPU去invalidate其相关的Cache line。 

两个地方可能除外: 
-> 如果采用write-through策略,则根本不存在缓存一致性问题(Linux对全部内存采用write-back策略); 
-> TLB也是Cache,但它的一致性(至少在IA32上)不能通过Snoopying技术解决,而是要发送 
INVALIDATE_TLB_VECTOR这个IPI给其它的CPU。 

4.4) 进一步解释,MESI协议 

包括IA32的许多体系结构的CPU,为了保证缓存一致性,实现了MESI协议。 

M: Modified,已修改 
E: Exclusive,排他 
S: Shared,共享 
I: Invalid,无效 

IA32 的CPU实现了MESI协议来保证Cache coherence。 CPU的总线监测单元,始终监视着总线上所有的内存写操作, 
以便随时调整自己的Cache状态。 

-> Modified。 本CPU写,则直接写到Cache,不产生总线事物;其它CPU写,则不涉及本CPU的Cache,其它CPU 
读,则本CPU需要把Cache line中的数据提供给它,而不是让它去读内存。 

-> Exclusive。只有本CPU有该内存的Cache,而且和内存一致。 本CPU的写操作会导致转到Modified状态。 

-> Shared。 多个CPU都对该内存有Cache,而且内容一致。任何一个CPU写自己的这个Cache都必须通知其它 
的CPU。 

-> Invalid。 一旦Cache line进入这个状态,CPU读数据就必须发出总线事物,从内存读。 


5) 考虑到DMA 

5.1). Wirte through策略。 这种情形比较简单。 

-> 本CPU写内存,是write through的,因此无论什么时候DMA读内存,读到的都是正确数据。 
-> DMA写内存,如果DMA要写的内存被本CPU缓存了,那么必须Invalidate这个Cache line。下次CPU读它,就 
直接从内存读。 

5.2). Write back策略。 这种情形相当复杂。 

-> DMA读内存。被本CPU总线监视单元发现,而且本地Cache中有Modified数据,本CPU就截获DMA的内存读操作, 
把自己Cache Line中的数据返回给它。 

-> DMA写内存。而且所写的位置在本CPU的Cache中,这又分两种情况: 
a@ Cache Line状态未被CPU修改过(即cache和内存一致),那么invalidate该cache line。 
b@ Cache Line状态已经被修改过,又分2种情况: 

<1> DMA写操作会替换CPU Cache line所对应的整行内存数据,那么DMA写,CPU则invalidate 
自己的Cache Line。 
<2> DMA写操作只替换Cache Line对应的内存数据的一部分,那么CPU必须捕获DMA写操作的新 
数据(即DMA想把它写入内存的),用来更新Cache Line的相关部分。
阅读(841) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~