起因 : 看见代码里调用 mb() ; 很疑惑
在 Linux 内核源码中,arch 文件夹存储了与特定架构相关的代码,该文件夹的名称是 “architecture” 的缩写。这些代码包括对底层硬件的处理、内核启动序列以及与操作系统交互的驱动程序等。
举例来说,如果你要编译适用于 ARM 架构的内核,就需要查看 arch/arm 目录下的代码,其中包括了针对 ARM 处理器体系结构的汇编代码、引导程序、设备树等。同样地,如果要编译适用于 x86 架构的内核,则需要查看 arch/x86 目录下的代码,其中包括了与 x86 指令集架构相关的代码、基于 BIOS 或 UEFI 的启动流程实现等。
在 Linux 内核源码中,/include/asm 文件夹存储了特定架构的头文件。该文件夹的名称是 “assembler” 的缩写,其目的是为了向内核代码提供与底层硬件和指令集相关的宏定义和函数原型等。
例如,如果要编译适用于 x86 架构的内核,就需要查看 arch/x86/include/asm/ 目录下的头文件,其中包括了与 x86 指令集架构相关的宏定义、函数原型以及 inline 汇编语句等。同样地,如果要编译适用于 ARM 架构的内核,则需要查看 arch/arm/include/asm目录下的头文件,其中包括了与 ARM 指令集架构相关的定义和实现等。
不同架构下mb()函数定义也不同,在看mb()函数资料时,找到一个相近的函数barrier()
选取一个,看具体到函数定义上
这里多了个 “l.msync”,先不管他,先看简单的 :
#define barrier() asm volatile("" : : : "memory");
asm 调用内联汇编程序
asm(
内嵌汇编指令
:输出操作数
:输入操作数
:破坏描述
);
内嵌汇编以 asm(); 格式表示,里面分成4个部分,内嵌汇编指令、输出操作数、输入操作数、破坏描述。各部分之间使用“:”分割。其中内嵌汇编指令是必不可少的,但可以为空。其他3部分根据程序需要可选。如果只有内嵌汇编指令时,后面的“:”可以省略
volatile是一个关键字,它的作用是告诉编译器不要对这段代码进行优化和重排。
具体来说,volatile关键字会提示编译器,这段代码中的变量可能会被意外修改,因此在编译器的优化过程中不能将其缓存到寄存器或者优化掉不必要的指令,以确保程序的正确性。如果不使用volatile,编译器可能会对变量进行优化,这可能会导致程序出现异常结果。
概念有点抽象,看看什么情况变量会被意外修改。
变量可能会被意外修改的情况通常包括:
(1)多线程编程:在一个多线程环境中,如果多个线程共享同一个变量并对其进行读写操作,那么这个变量就有可能被另一个线程修改。此时,需要使用volatile关键字来确保程序的正确性。
(2)中断处理程序:在中断处理程序中,如果多个中断同时访问同一个变量,那么这个变量也可能会被意外修改。因此,在中断处理程序中定义的变量通常都是“易失(volatile)”的。
(3)存储器映射的I/O设备:对于存储器映射的I/O设备,例如串口、网卡等,由于数据可能会在不同的时刻到达,因此需要使用volatile关键字来确保代码的正确性。
(4)除了上述情况,还有一些特定的硬件场景或者需要与外部软件进行交互的场景也可能需要使用volatile关键字。
只要存在多个操作可能同时访问和修改同一个变量的情况,就有可能出现变量被意外修改的情况,此时需要使用volatile关键字来确保程序的正确性。
使用volatile虽然可以避免由于编译器优化带来的问题,但也可能会降低程序的运行效率。因此,只有在必要时才应该使用volatile,应权衡好程序的正确性和运行效率之间的关系。
总结:volatile用于告诉编译器,严禁将此处的asm汇编语句与它之前和之后的c语句在编译时重组合。
:::表示这条指令没有输入或输出操作数,但它之后的“memory”有同步内存的副作用。
“memory”参数指定该指令具有内存屏障的副作用,防止编译器和CPU跨内存屏障对内存访问进行重排。
“memory”就是通知GCC编译器,此段内嵌汇编修改了memory中的内容,asm之前的c代码块和之后的c代码块看到的memory可能是不一样的,对memory的访问不能依赖之前的缓存,需要重新加载。
asm volatile("" : : : "memory")
这是一条空的内联汇编代码,它告诉编译器在这个位置插入一个内存屏障(memory barrier)。这个内存屏障会阻止编译器和处理器对内存访问做出过于聪明的优化,确保指令执行的顺序符合程序员的意愿,从而避免出现意料之外的错误。
“memory”就是通知GCC编译器,此段内嵌汇编修改了memory中的内容,asm之前的c代码块和之后的c代码块看到的memory可能是不一样的,对memory的访问不能依赖之前的缓存,需要重新加载。
#define mb() asm volatile ("l.msync" ::: "memory")
多了个"l.msync"
具体来说,它定义了宏mb()来执行l.msync汇编指令,这是一条加载内存同步指令,确保在继续执行后续指令之前,之前的所有加载都已经完成。
#define mb() asm volatile("sync" : : : "memory");
“sync”指令用于同步处理器和其他设备之间的内存访问,确保所有待决的内存操作都已经完成。
不止有mb,还有rmb()和wmb(),说白了就是单独看读或写,下面x86架构下源代码定义
在其他的一些架构下wmb等于mb或是等于barrier,也是有可能的
linux源码中,mb() 函数、barrier()函数和wmb()函数这三者的区别和共同点
这三个函数都是用于同步内存访问的。
mb()函数(Memory Barrier)用于确保在调用这个函数之前和之后的内存访问操作按照正确的顺序执行,以避免编译器或处理器的优化导致乱序执行。
wmb()函数(Write Memory Barrier)与mb()函数类似,但只确保写内存访问操作按照正确的顺序执行。它通常用于同步对设备寄存器的写入。
barrier()函数表示一个完整的内存屏障,可以同时保证读和写的同步,相当于mb()和wmb()的组合。它通常用于在多核系统中同步共享变量的读写操作。
因此,它们的主要区别是:
mb()确保所有的读和写操作都被同步
wmb()仅确保写操作被同步
barrier()同时确保读和写操作被同步
它们的共同点是:它们都被设计用来防止编译器或处理器的指令重排等优化带来的副作用,确保内存访问操作按照正确的顺序执行。
其它链接辅助理解:
内存屏障(asm volatile(“”: : :“memory”))
C语言中内嵌汇编asm语法
asm volatile ( " instructions " ) 语法