内存屏障(__asm__ __volatile__("": : :"memory"))

本文深入探讨了内存屏障的概念、作用及其在不同平台上的实现方式,包括如何通过汇编指令和宏函数确保程序顺序一致性和内存操作的正确执行。
内存屏障(memory barrier)
#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> 首先,用户的问题是:“我想查询 __ASM volatile("" ::: "memory") 的使用方法 请问__ASM volatile("" ::: "memory") 的使用方法” 用户提供了几个引用: - 引用[1]:解释了 __asm__ __volatile__ 的结构,包括汇编语句模板、输出、输入、破坏描述部分,使用 ":" 分隔。 - 引用[2]:类似地介绍了内嵌汇编语法,并提到 __asm__ volatile ("": : :"memory") 是用于内存屏障。 - 引用[3]:给出了一个代码示例,其中使用了 __asm__ __volatile__ (“” : : :memory”),并描述了在内存操作之间插入它来确保顺序。 - 引用[4]:解释了 volatile 关键字的作用,防止编译器优化掉指令。 用户在上一次的问题中提到了这些引用,所以我需要在回答中参考它们。系统指令说:生成相关问题,并在回答中引用的段落末尾自然地添加引用标识。 关键点是解释 __asm__ volatile("" : : : "memory") 的使用方法。 这是一个内存屏障memory barrier)。它告诉编译器不要优化内存访问顺序,确保内存操作在屏障前后不被重排序。 使用方法: - 格式:__asm__ volatile("" : : : "memory") - 这是GCC内嵌汇编语法。 - 汇编语句模板为空字符串,表示没有实际的汇编指令。 - 输出部分和输入部分为空。 - 破坏描述部分为"memory",表示内存被修改,编译器不能假设内存内容在汇编指令后不变。 主要用途: - 防止编译器优化时重排内存访问指令。 - 在并发编程中,用于确保内存操作的顺序性,避免缓存一致性问题。 - 例如,在多线程代码中,当修改共享变量后,使用它来强制刷新内存视图。 从引用[3]的代码示例: - 插入 __asm__ __volatile__ (“” : : :memory”) 在内存操作之间,确保d[0]的修改和d[1]的修改不会被编译器重排,并且内存内容被正确更新。 回答结构: - 先解释这个语句是什么。 - 然后描述其使用方法。 - 最后生成相关问题。 系统指令要求: - 所有行内数学表达式用 $...$(但这个问题不涉及数学表达式)。 - 独立公式用 $$...$$(可能不适用)。 - LaTeX语法正确(但这里主要是文本)。 - 使用中文回答。(用户的问题是中文,所以回答用中文。) - 生成相关问题。 - 在回答中引用的段落末尾添加引用标识,例如[^1]。 所以,在引用引用内容时,添加[^1]、[^2]等。 组织回答: 1. 介绍 __asm__ volatile("" : : : "memory") 是什么。 2. 解释其语法结构,参考引用[1]和[2]。 3. 描述其作用和使用场景,参考引用[3]和[4]。 4. 提供简单示例或解释。 5. 结束部分生成相关问题。 相关问题: - 可以从用户引用中提取,比如优化影响、具体用例等。 相关问题想法: 1. 内存屏障的类型有哪些? 2. GCC内嵌汇编的其他常见用法? 3. 如何避免多线程中的内存问题? 4. __volatile__ 关键字在其他上下文中的作用? 确保回答真实可靠:基于提供的引用和自己的知识。 最终回答结构: - 开头直接回答。 - 详细解释。 - 自然地添加引用标识。 - 最后生成相关问题。 </think> ### __asm__ volatile("" : : : "memory") 的使用方法 __asm__ volatile("" : : : "memory") 是 GCC 编译器内嵌汇编语法中的一个内存屏障memory barrier)。它主要用于防止编译器优化时重排内存访问指令,确保内存操作(如读写共享变量)的顺序性,尤其在并发编程或硬件操作中避免缓存一致性问题。下面我将基于您的引用逐步解释其使用方法、结构和应用场景。 #### 1. **基本语法和结构** GCC 内嵌汇编的基本格式为: `__asm__ __volatile__(汇编语句模板: 输出部分: 输入部分: 破坏描述部分)`。各部分用冒号 `:` 分隔: - **汇编语句模板**:这里是空字符串 `""`,表示没有实际汇编指令生成,仅作为占位符。 - **输出部分**:空(用 `:` 表示无输出操作数)。 - **输入部分**:空(用 `:` 表示无输入操作数)。 - **破坏描述部分**:`"memory"`,这告诉编译器:内存内容可能被修改,因此编译器不能优化或重排内存访问指令(例如,不能缓存变量值或改变读取顺序)[^1][^2]。 示例完整语句:`__asm__ volatile("" : : : "memory")`。其中: - `__volatile__` 关键字确保编译器不要优化掉这个内联汇编表达式(即保留原样),即使使用了编译优化选项如 `-O`[^4]。 - 在 C 代码中,它通常单独写成一行,作为一个无操作(no-op)的内存屏障。 #### 2. **主要作用和使用场景** - **防止内存访问重排序**:编译器在优化时可能重新排序内存操作以提高性能,但在多线程或硬件交互中,这可能导致错误。`"memory"` 破坏描述强制编译器: - 屏障前的所有内存操作(如写变量)必须在屏障前完成。 - 屏障后的所有内存操作必须在屏障后才执行。 - 确保内存内容的最新值被刷新,避免使用缓存旧值。 例如,在引用[3]的代码中: ```c int c(int *d, int *e) { int r; d[0] += 1; // 修改 d[0] 的值 r = e[0]; // 读取 e[0] 的值 __asm__ __volatile__ ("" : : : "memory"); // 内存屏障 d[1] += 1; // 修改 d[1] 的值 return r; } ``` 这里,屏障确保 `d[0] += 1` 和 `r = e[0]` 不会被重排到 `d[1] += 1` 之后,从而避免并发访问时的数据不一致问题[^3]。 - **常见应用场景**: - **多线程同步**:在修改共享变量后插入屏障,防止其他线程看到部分更新状态。 - **设备驱动程序**:确保硬件寄存器的读写顺序不被编译器优化干扰。 - **无锁编程**:结合原子操作实现高效并发。 - **注意事项**: - 它只影响编译器的优化行为,不生成额外汇编指令(汇编模板为空),所以性能开销小。 - 对于 CPU 级别的内存屏障(如 x86 的 `mfence`),可能需要实际汇编指令;而 `"memory"` 主要针对编译器优化。 #### 3. **使用示例** 在 C 代码中,您可以这样使用: ```c #include <stdio.h> void update_counter(int *counter) { (*counter)++; // 增加计数器值 __asm__ volatile("" : : : "memory"); // 插入内存屏障 printf("Counter updated: %d\n", *counter); // 确保打印的是最新值 } int main() { int count = 0; update_counter(&count); return 0; } ``` - 如果在多线程环境中,`__asm__ volatile("" : : : "memory")` 可以防止 `(*counter)++` 和 `printf` 被重排,确保其他线程看到正确的计数更新。 - 在编译时,即使使用 `-O2` 选项,编译器也不会优化掉这个屏障[^4]。 #### 4. **为什么需要它?** 编译器优化(如指令重排)可能破坏代码的内存顺序语义。`"memory"` 破坏描述相当于告诉 GCC:“不要假设内存内容不变,必须重新加载变量值”。这在低层编程中至关重要,例如操作系统内核或嵌入式系统[^2][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值