内存屏障(memory barrier)

本文深入探讨了Linux系统中内存屏障的实现方式,包括不同类型的内存屏障及其作用,如wmb(), rmb(), mb()等,并详细解析了这些屏障背后的汇编指令及优化策略。
转载于http://blog.chinaunix.net/u3/90973/showart_2120302.html

首先看看 __set_current_state() 和set_current_state()函数的区别
#define __set_current_state(state_value) /
do { current->state = (state_value); } while (0)

#define set_current_state(state_value) /
set_mb(current->state, (state_value))

#define set_mb(var, value) do { var = value; mb(); } while (0)
#define mb() __asm__ __volatile__ ("" : : : "memory")

1)set_mb(),mb(),barrier()函数追踪到底,就是__asm__ __volatile__("":::"memory"),而这行代码就是内存屏障。
2)__asm__用于指示编译器在此插入汇编语句
3)__volatile__用于告诉编译器,严禁将此处的汇编语句与其它的语句重组合优化。即:原原本本按原来的样子处理这这里的汇编。
4)memory 强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registers和cache中已缓存的内存单元中的数据将作废。cpu将不 得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registers,cache中的数据用于去优化指令,而避免去访问内存。
5)"":::表示这是个空指令。barrier()不用在此插入一条串行化汇编指令。在后文将讨论什么叫串行化指令。
6)__asm__,__volatile__,memory在前面已经解释

在 linux/include/asm-i386/system.h定义:
#define mb() __asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory")
7)lock前缀表示将后面这句汇编语句:"addl $0,0(%%esp)"作为cpu的一个内存屏障。
8)addl $0,0(%%esp)表示将数值0加到esp寄存器中,而该寄存器指向栈顶的内存单元。加上一个0,esp寄存器的数值依然不变。即这是一条无用的汇编 指令。在此利用这条无价值的汇编指令来配合lock指令,在__asm__,__volatile__,memory的作用下,用作cpu的内存屏障。
9)set_task_state()带有一个memory barrier,set_task_state()肯定是安全的,但 __set_task_state()可能会快些。


关于barrier()宏实际上也是优化屏障:
#define barrier() __asm__ __volatile__("": : :"memory")
CPU越过内存屏障后,将刷新自己对存储器的缓冲状态。这条语句实际上不生成任何代码,但可使gcc在barrier()之后刷新寄存器对变量的分配。      
             例1:
                1        int a = 5, b = 6;
                2        barrier();
                3        a = b;
       
            在line 3,GCC不会用存放b的寄存器给a赋值,而是重新读内存中的b值,赋值给a。
            
            
例2:
它在进程上下文中将一个元素插入一个单向链表:
new->next=i->next;
wmb();
i->next=new;
同时,如果不加锁地遍历这个单向链表。或者在遍历链表时已经可以看到new,或者new还不在该链表中。两个内存写
事件的顺序必须按照程序顺序进行。否则可能new的next指针将指向一个无效地址,就很可能出现 OOPS!

不 论是gcc编译器的优化还是处理器本身采用的大量优化,如Write buffer, Lock-up free, Non- blocking reading, Register allocation, Dynamic scheduling, Multiple issues 等,都可能使得实际执行可能违反程序顺序,因此,引入内存屏障来保证事件的执行次序严格按程序顺序来执行。

使 用内存屏障强加的严格的CPU内存事件次序,保证程序的执行看上去象是遵循顺序一致性模型。在当前的实现中,wmb() 实际上是一个空操作,这是因为目前Intel的CPU系列都遵循“处理机一致性”,所有的写操作是遵循程序顺序的,不会越过前面的读写操作。但是,由于 Intel CPU系列可能会在将来采用更弱的内存一致性模型并且其他体系结构可能采用其他放松的一致性模型,仍然在内核里必须适当地插入wmb()保证内存事件的正 确次序。



在linux/include/asm-i386/alternative.h定义如下:
#define alternative(oldinstr, newinstr, feature) /
asm volatile ("661:/n/t" oldinstr "/n662:/n" /
".section .altinstructions,/"a/"/n" /
" .align 4/n" /
" .long 661b/n" /* label */ /
" .long 663f/n" /* new instruction */ /
" .byte %c0/n" /* feature bit */ /
" .byte 662b-661b/n" /* sourcelen */ /
" .byte 664f-663f/n" /* replacementlen */ /
".previous/n" /
".section .altinstr_replacement,/"ax/"/n" /
"663:/n/t" newinstr "/n664:/n" /* replacement */ /
".previous" :: "i" (feature) : "memory")


1.alternative()宏用于在不同的cpu上优化指令。oldinstr为旧指令,newinstr为新指令,feature为cpu特征位。
2.oldinstr的长度必须>=newinstr的长度。不够将填充空操作符。

注:这是源代码注释,具体我也看不懂

在linux/include/asm-i386/system.h定义如下:
#define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)
#define rmb() alternative("lock; addl $0,0(%%esp)", "lfence", X86_FEATURE_XMM2)

#define read_barrier_depends() do { } while(0)

#ifdef CONFIG_X86_OOSTORE
/* Actually there are no OOO store capable CPUs for now that do SSE,but make it already an possibility. */
-->OOO:Out of Order,乱序执行。
-->SSE:SSE是英特尔提出的即MMX之后新一代(当然是几年前了)CPU指令集,最早应用在PIII系列CPU上。
本小段内核注释意即:乱序存储的cpu还没有问世,故CONFIG_X86_OOSTORE也就仍未定义的,wmb()当为后面空宏(在__volatile__作用下,阻止编译器重排顺序优化)。

#define wmb() alternative("lock; addl $0,0(%%esp)", "sfence", X86_FEATURE_XMM)
#else
#define wmb() __asm__ __volatile__ ("": : :"memory")
#endif

1.lock 前缀表示将后面这句汇编语句:"addl $0,0(%%esp)"作为cpu的一个内存屏障。addl $0,0(%%esp)表示将数值0加到esp寄存器中,而该寄存器指向栈顶的内存单元。加上一个0,esp寄存器的数值依然不变。即这是一条无用的汇编 指令。在此利用这条无价值的汇编指令来配合lock指令,用作cpu的内存屏障。

2.mfence保证系统在后面的memory访问之前,先前的memory访问都已经结束。这是mfence是X86cpu家族中的新指令。

3.新旧指令对比:
以前的源代码:
#define mb() __asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory")
__asm__用于指示编译器在此插入汇编语句
__volatile__用于告诉编译器,严禁将此处的汇编语句与其它的语句重组合优化。即:原原本本按原来的样子处理这这里的汇编。

现在的源代码:
#define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)
两者比较:
比起以前的源代码来少了__asm__和__volatile__。增加了alternative()宏和mfence指令。


而 SFENCE指令(在Pentium III中引入)和LFENCE,MFENCE指令(在Pentium 4和Intel Xeon处理器中引入)提供了某些特殊类型内存操作的排序和串行化功能。sfence,lfence,mfence指令是在后继的cpu中新出现的的指 令。

SFENCE,LFENCE,MFENCE指令提供了高效的方式来保证读写内存的排序,这种操作发生在产生弱排序数据的程序和读取这个数据的程序之间。
SFENCE——串行化发生在SFENCE指令之前的写操作但是不影响读操作。
LFENCE——串行化发生在SFENCE指令之前的读操作但是不影响写操作。
MFENCE——串行化发生在MFENCE指令之前的读写操作。
注意:SFENCE,LFENCE,MFENCE指令提供了比CPUID指令更灵活有效的控制内存排序的方式。

sfence:在sfence指令前的写操作当必须在sfence指令后的写操作前完成。
lfence:在lfence指令前的读操作当必须在lfence指令后的读操作前完成。
mfence:在mfence指令前的读写操作当必须在mfence指令后的读写操作前完成。

其实这里是用mfence新指令来替换老的指令串:__asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory")。
mfence 的执行效果就等效于 __asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory")的执行效果。只不过,__asm__ __volatile__ ("lock; addl $0,0(%%esp)": : :"memory")是在以前的cpu平台上所设计的,借助于编译器__asm__,__volatile__,lock这些指令来实现内存屏障。而在 Pentium 4和Intel Xeon 处理器中由于已经引入了mfence指令,无须再用这一套指令,直接调用这一条指令即ok。而alternative()宏就是用于这个优化指令的替换, 用新的指令来替换老的指令串。


这些函数在已编译的指令流中插入硬件内存屏障;具体的插入方法是平台相关的。rmb(读内存屏障)保证了屏障之前的读操作一定会在后来的读操作执行之前完成。wmb 保证写操作不会乱序,mb 指令保证了两者都不会。这些函数都是 barrier函数的超集。
内 存屏障出现因为编译器或现在的处理器常会自作聪明地对指令序列进行一些处理,比如数据缓存,读写指令乱序执行等等。如果优化对象是普通内存,那么一般会提 升性能而且不会产生逻辑错误。但如果对 I/O操作进行类似优化很可能造成致命错误。所以要使用内存屏障,以强制该语句前后的指令以正确的次序完成。其实在指令序列中放一个wmb的效果是使得指 令执行到该处时,把所有缓存的数据写到该写的地方,同时使得wmb前面的写指令一定会在wmb的写指令之前执行。
<think>嗯,用户这次问的是内存屏障的作用,看来是在深入理解计算机底层机制。之前连续讨论了AUTOSAR和内存泄漏,现在转向系统级编程的并发控制,说明用户可能是嵌入式开发者或系统程序员,正在排查多线程环境下的同步问题。 内存屏障这个概念确实容易让人困惑,需要从硬件和编译器两个层面解释清楚。用户可能实际遇到过因内存乱序导致的诡异bug,比如数据明明更新了却读到旧值。我得用汽车装配线的例子类比CPU流水线,这样更直观。 硬件优化带来的指令重排是问题的核心。现代CPU为了效率会乱序执行,比如Store Buffer的存在可能让写操作延迟,导致其他核看到操作的顺序和代码顺序不一致。内存屏障就是强行给这些“调皮”的优化踩刹车,告诉CPU“到此为止之前的操作必须完成”。 编译器优化也是隐患。比如用volatile只能阻止编译器重排,但管不了CPU层面的乱序。这点在无锁编程中特别致命,用户可能正为此头疼。需要强调屏障是软硬双管齐下的解决方案。 用户应该还想知道具体使用场景,所以示例代码里要展示经典的双检锁模式。这个模式如果漏掉屏障,单例可能被初始化两次——这种bug极难复现但危害巨大。另外内核态的barrier()宏和用户态的__sync_synchronize()区别也得提一下。 最后的提问设计要层层递进:从原理(为什么需要)到实践(怎么用),再到特定领域(Linux内核)和替代方案。第五个问题特意问编译器屏障,因为很多人会混淆它与硬件屏障的作用范围。</think>内存屏障Memory Barrier)的主要作用是**强制保证内存操作的顺序性和可见性**,解决多核/多线程环境下因**指令重排序**和**缓存一致性**引发的数据一致性问题。其核心作用如下: --- ### 核心作用 1. **禁止指令重排序** - 编译器和CPU可能为优化性能而乱序执行指令(如写操作延迟、读操作提前)。 - 内存屏障会阻止屏障前后的指令跨越屏障执行,确保程序逻辑顺序。 2. **保证内存可见性** - 强制将缓存数据刷新到主存(写屏障),或从主存加载最新数据(读屏障)。 - 确保一个线程的修改对其他线程立即可见。 3. **同步多核数据一致性** - 在弱一致性内存模型中(如ARM、PowerPC),屏障触发缓存失效/更新,维持多核缓存一致。 --- ### 类型与场景 | 屏障类型 | 作用 | 典型场景 | |-------------------|----------------------------------------------------------------------|--------------------------| | **写屏障(Store Barrier)** | 确保屏障前的所有写操作完成,数据刷入主存后才执行后续操作 | 修改共享数据后释放锁 | | **读屏障(Load Barrier)** | 确保后续读操作前,先加载主存最新数据(清空本地缓存) | 获取锁后读取共享数据 | | **全屏障(Full Barrier)** | 同时具备读写屏障功能(如 `mfence`),确保屏障前后指令无重排且全局可见 | 无锁数据结构、内核同步原语 | --- ### 代码示例(无锁编程中的屏障) ```c // 共享数据 int data = 0; bool ready = false; // 线程A:写入数据 void thread_A() { data = 42; // 写操作 // 写屏障:确保data写入完成后再更新ready __sync_synchronize(); // GCC内置全屏障 ready = true; // 标志位更新 } // 线程B:读取数据 void thread_B() { while (!ready); // 等待ready为true // 读屏障:确保读取data前获取最新值 __sync_synchronize(); printf("%d", data); // 必须输出42而非0或随机值 } ``` > **关键点**:若省略屏障,线程B可能因重排序先看到 `ready=true` 后看到 `data=0`(脏读)。 --- ### 典型应用场景 - **多线程同步**:锁实现(如自旋锁)、条件变量。 - **无锁数据结构**:CAS(Compare-And-Swap)操作前后插入屏障。 - **设备驱动**:确保硬件寄存器按顺序写入。 - **内核同步原语**:如Linux的 `smp_mb()`、`rmb()`、`wmb()`。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值