免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1662 | 回复: 0
打印 上一主题 下一主题

LKML上一篇关于barrier文档草案的讨论 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-05-14 15:23 |只看该作者 |倒序浏览

                               
xiaozhaoz
回复于:2006-03-13 19:55:04

第一次总结:
现在常说的SMP是共享总线结构,同时共享memory, memory可能是内存,也可能是cache。
对于单CPU而言,CPU cache中的内容和memory中的内容的同步要注意:
1. 虽然CPU可能会更改执行顺序,但CPU更改后的指令在UP环境中是正确的。
2. CPU中的cache和memory的同步只需要考虑 DMA和CPU同时对memory访问导致的同步问题,这种问题要在编写驱动的时候使用合适的指令和mb来保证。也就是说,在UP中,只需要考虑cpu和dma的同步问题。
在SMP中,需要考虑CPU之间,CPU和dma之间的同步问题。
上面我们讨论的都是CPU之间的同步问题。
SMP是一个共享总线的结构,一般来说,存在两层总线, host总线和PCI总线或者其它IO总线。
host总线连接多个CPU和内存,host/PCI桥 (就是通常说的北桥)
PCI总线连接host/PCI桥和 PCI主从设备,及PCI/Isa桥。 就是通常说的南桥。
由此可见,PCI设备要将自己的register map到内存中,需要通过host/pci bridge, 要靠host/pci bridge访问host总线,然后到达内存。内存的映射和访问这些工作由bridge+dma完成。
而多个CPU要访问内存,也要通过host总线。
由上可见, 一个CPU或者DMA要访问内存,必须锁总线,总线是共享的。同样为了使得内存的修改能被其它设备知晓,必须用signal通知机
制,某个设备修改了内存,必须有监听总线的机制,然后通过某个signal通知到设备,如dma访问内存的时候,cpu监控总线, 用HIT和HITM通
知cpu修改的内容命令cache, 所以相关cache要invalidate,一般是64bit。这个过程是一级一级cache往上走的过程。
为了防止dma中的数据cache在CPU中,大家一般采用申明为volatile的方法,这种方法会导致效率不高,CPU每次必须lock 总线,访问内存才能获得相应的内容。
上面介绍的都是硬件相关的东西。
软件上,代码执行顺序的更改可能被编译器和CPU更改。
为了保证访问内存代码按照指定顺序执行,必须使用smp_*mb*()宏。
在单CPU中,smp_*mb*()只是一个compiler barrier,仅仅是防止编译器错误地优化访问内存代码:
#define barrier() __asm__ __volatile__("": : :"memory")
volatile告诉编译器,这段代码不能忽略, "memory" 是编译器的clobber,告诉编译器,
1. 内存信息已经修改,在这条指令后面的寄存器的值必须从内存中重新获取
2. 代码的先后顺序必须按照原有的产生汇编代码

在SMP中,smp_*mb*()是一个hardware barrier和compiler barrier的组合
#define smp_mb()        mb()
#define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)
alternative()用来保持CPU指令兼容,在P4以前的CPU没有mfence指令,用lock; addl $0,0(%%esp)指令。alternative()包含"memory" clobber,所以包含compiler barrier功能。
lock的作用是发出lock信号,占用host总线,同时其它的CPU会监听总线。invalidate cache中的相应内容,一般每项64bit。
在哪些情况下需要使用memory barrier,参考:
http://www.linuxjournal.com/article/8211
http://www.linuxjournal.com/article/8212
和附件的文档
关于I/O DMA和CPU的memory barrier问题,欢迎大家继续讨论。
还有一个问题,为什么Linux发行版本中的glibc库没有分UP和SMP版本? 正常来说,已经编译成二进制的系统库应该UP和SMP不兼容啊,因为锁的实现等都要靠mb.
也写一下自己的总结:
       
        内核中定义的内存屏障原语有:
               
                #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协议
               
               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种情况:
                                       
                                         DMA写操作会替换CPU Cache line所对应的整行内存数据,那么DMA写,CPU则invalidate
                                            自己的Cache Line。
                                         DMA写操作只替换Cache Line对应的内存数据的一部分,那么CPU必须捕获DMA写操作的新
                                            数据(即DMA想把它写入内存的),用来更新Cache Line的相关部分。
               
               
               
               
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/72704/showart_1928408.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP