C和C++中的volatile、内存屏障和CPU缓存一致性协议MESI

栏目: C++ · 发布时间: 5年前

内容简介:1)

目录

3. volatile 应用场景 3

4. 内存屏障( Memory Barrier 4

1) 结果 1 (非优化编译: g++ -g -o x x.cpp -O0 5

2) 结果 2 (优化编译: g++ -g -o x x.cpp -O2 6

6. 不同 CPU 架构的一致性模型 6

8. C++ 标准库对内存顺的支持 7

1 CPU 、缓存和主存 8

第三级缓存( L3 Cache )多核共享: 8

2 SMP 对称多处理器结构 9

1) C++ 标准委员会( The C++ Standards Committee 10

4) Linux 内核关于 volatile 的说明 10

5) Intel 内存模型( Intel Memory Model 10

6) Intel TSO 内存模型 10

7) Sequential Consistency &TSO 10

9) x86-64 IA-32 开发手册 10

10) 编译器屏障( Compiler Barriers 10

11) C ++ 11 memory_order_consume 的作用 10

12) MESI (多核 CPU 缓存一致性协议) 10

13) MESIF (多核 CPU 缓存一致性协议) 10

1. 

本文内容主要针对 Linux ,而且主要是 x86 环境。先看一常见用法:

class Thread {

public:

X()

: _stop (false) {

}

void stop() {

_stop  = true;

}

void run() {

while (! _stop ) {

work();

}

}

private:

volatile bool _stop ;

};

然后看看 标准 C++ 基金会 https://isocpp.org )怎么说的( 官方链接 ):

C和C++中的volatile、内存屏障和CPU缓存一致性协议MESI

2.  结论

1) 与平台无关的多线程程序, volatile 几乎无用( Java C# 中的 volatile 除外);

2) volatile 不保证原子性(一般需使用 CPU 提供的 LOCK 指令);

3) volatile 不保证执行顺序;

4) volatile 不提供内存屏障( Memory Barrier )和内存栅栏( Memory Fence );

5) 多核环境中内存的可见性和 CPU 执行顺序不能通过 volatile 来保障,而是依赖于 CPU 内存屏障。

注: volatile 诞生于单 CPU 核心时代,为保持兼容,一直只是针对编译器的,对 CPU 无影响。

volatile C/C++ 中的作用:

1) 告诉编译器不要将定义的变量优化掉;

2) 告诉编译器总是从缓存取被修饰的变量的值,而不是寄存器取值。

就前言中的代码,可移植的实现方式为:

#include< atomic >

class Thread {

public:

X()

: _stop (false) {

}

void stop() {

_stop  = true;

}

void run() {

while (! _stop ) {

work();

}

}

private:

std::atomic< bool >   _stop ;

};

不过这要求至少 C++11 ,否则可使用兼容 C++98 的实现 CAtomic 替代:

https://github.com/eyjian/libmooon/blob/master/include/mooon/sys/atomic.h ,实际上 Linux 内核源代码都带有这些基础设施。

3.  volatile 应用场景

1) 信号处理程序;

2) 与硬件打交道(嵌入式开发用得多);

3) 和 setjmp longjmp 配合(请参见: http://www.cplusplus.com/reference/csetjmp/setjmp/ ),原因同信号处理。

4.  内存屏障( Memory Barrier

内存屏障,也叫内存栅栏( Memory Fence )。分编译器屏障( Compiler Barrier ,也叫优化屏障)和 CPU 内存屏障,其中编译器屏障只对编译器有效,它们的定义如下表所示(限 x86 ,其它架构并不相同):

#define barrier() \

__asm__ __volatile__("":::"memory")

编译器屏障,如果是GCC,则可用__sync_synchronize()替代

#define mb() \

__asm__ __volatile__(" mfence ":::"memory")

CPU内存屏障,还分读写内存屏障

#define rmb() \

__asm__ __volatile__(" lfence ":::"memory")

CPU读(Load)内存屏障

#define wmb() \

__asm__ __volatile__(" sfence ":::"memory")

CPU写(Store)内存屏障

x86 架构的 CPU 内存屏障代码可在内核源代码的 arch/x86/include/asm/barrier.h 中找到。对于原子操作,需要使用 CPU 提供的“ lock ”指令,对于 CPU 乱序需使用 CPU 内存屏障。

代码顺序

编译器顺序

CPU顺序

a=1;

b=x;

c=z;

d=2;

a=1;

d=2;

b=x;

c=z;

b=x;

c=z;

a=1;

d=2;

推荐资料:

https://mariadb.org/wp-content/uploads/2017/11/2017-11-Memory-barriers.pdf

内存屏障进一步还分读内存屏障和写内存屏障等,对非内核开发者来说,内存屏障的主要应用场景为无锁编程( Lock-free ),因为像 pthread_mutx_t 等实际已经包含了“ Lock ”和“ Memory Barrier ”,所以无需再操心。

5.  setjmp longjmp

C/C++ 中, goto 关键词只能函数内的局部跳转,函数间的跳转需要使用 setjmp longjmp ,这也是有些协程库基于 setjmp longjmp 实现的原因。

1) setjmp

保存上下文,包括信号掩码,类似于 setcontext 。该函数的返回值比较特别,第一次返回 0 ,第二次返回的 longjmp 第二个参数值(如果 longjmp 第二个参数值为 0 ,则返回值为 1 ,这样方便区分于第一次返回)。

2) longjmp

该函数 从不返回 ,而是跳回到 setjmp 保存点,类似于 swapcontext 。如果没有先调用 setjmp ,则 longjmp 的行为是未定义的。 C++ 代码可能还会执行栈展开( Unwinding ),如果调用了任何非平凡析构函数( non-trivial destructors ,需显示处理的析构函数,如内存释放),也会导致未定义的行为。

3) 代码示例(摘自 http://www.cplusplus.com/reference/csetjmp/setjmp/

/* x.cpp */

/* setjmp example: error handling */

#include      /* printf, scanf */

#include     /* exit */

#include     /* jmp_buf, setjmp, longjmp */

struct X {

X() { fprintf(stderr, "X::ctor\n"); }

~X() { fprintf(stderr, "X::dtor\n"); }

};

int main()

{

jmp_buf env;

int val = -1;

int m = -1;

volatile int n = -1;

X x;

val = setjmp (env);

fprintf(stderr, "setjmp return: %d\n", val);

if (val) {

fprintf(stderr, "m: %d\n", m);

fprintf(stderr, "n: %d\n", n);

exit(val);

}

else {

m = 2018;

n = 2018;

/* code here */

longjmp (env, 19);   /* signaling an error */

return 0;

}

}

上例代码运行有两种输出结果:

1)  结果 1 (非优化编译: g++ -g -o x x.cpp -O0

X::ctor

setjmp return: 0

setjmp return: 19

m: 2018

n: 2018

非优先编译时,总是从内存取值。

2)  结果 2 (优化编译: g++ -g -o x x.cpp -O2

X::ctor

setjmp return: 0

setjmp return: 19

m: -1

n: 2018

m 未加 volatile 修饰,直接读取寄存器值,因此结果是 -1 。从这里也可以看出,即使是单线程程序, volatile 也是必要的,也说明 volatile 并不是完全没用,只是它不能帮助解决多线程的原子性、内存屏障和 CPU 乱序执行。

另外可发现,上列代码的类 X 的析构未执行,但若将 exit 改成 return ,则会执行类 X 的析构,遇到“ } ”和“ return ”时,编译器会安插析构函数调用。

6.  不同 CPU 架构的一致性模型

注: LOAD 操作, STORE 操作。

Loads reordered after loads

Loads reordered after stores

Stores reordered after stores

Stores reordered after loads

Atomic reordered with loads

Atomic reordered with stores

Dependent loads reordered

Incoherent instruction cache pipeline

Alpha

Y

Y

Y

Y

Y

Y

Y

Y

ARMv7

Y

Y

Y

Y

Y

Y

Y

PA-RISC

Y

Y

Y

Y

POWER

Y

Y

Y

Y

Y

Y

Y

SPARC RMO

Y

Y

Y

Y

Y

Y

Y

SPARC PSO

Y

Y

Y

Y

SPARC TSO

Y

Y

x86

Y

Y

x86 oostore

Y

Y

Y

Y

Y

AMD64

Y

IA-64

Y

Y

Y

Y

Y

Y

Y

z/Architecture

Y

四种 SMP 架构的 CPU 内存一致性模型:

1) 顺序一致性模型( SC Sequential Consistency ,所有读取和所有写入都是有序的);

2) 宽松一致性模型( RC Relaxed Consistency ,允许某些可以重排序) ARM POWER 属于这类;

3) 弱一致性模型( WC Weak Consistency ,读取和写入任意重新排序,仅受显式内存屏障限制);

4) 完全存储排序( TSO Total Store Ordering ), SPARC x86 属于这种类型,只有“ store load ”一种情况会发生重排序,其它情况和 SC 模型一样。

7.  x86-TSO

x86-TSO Intel 推出的一种 CPU 内存一致性模型,特点是只有“ Store Load ”一种情况会重排序,也就是“ Load ”可以排(乱序)在“ Store ”的前面,因此不需要“ Load Load ”、“ Store Store ”和“ Load Store ”这三种屏障。

1) “ Store Load ”屏障的作用是:确保“前者刷入内存”的数据对“后者加载数据”是可见的;

2) “ Load Load ”屏障的作用是:确保“前者装载数据”先于“后者装载指令”;

3) “ Store Store ”屏障的作用是:确保“前者数据”先于“后者数据”刷入内存,且“前者刷入内存的数据”对“后者是可见的”;

4) “ Load Store ”屏障的作用是:确保“前者装载数据”先于“后者刷新数据到内存”。

C和C++中的volatile、内存屏障和CPU缓存一致性协议MESI

8.  C++ 标准库对内存顺的支持

1)  头文件

enum memory_order  {

memory_order_relaxed, // 宽松一致性模型,不对执行顺序做任何保证

memory_order_consume , // (读操作)本线程所有后续有关本操作的必须在本操作完成后执行

memory_order_acquire, // (读操作)本线程所有后续的读操作必须在本条操作完成才能执行

memory_order_release, // (写操作)本线程所有之前的写操作完成后才执行本操作

memory_order_acq_rel, // (读-修改-写)同时包含Acquire和Release

memory_order_seq_cst   // (读-修改-写,默认类型)顺序一致性模型,全部顺序执行

};

2)  头文件

// 默认内存顺类型为“memory_order_seq_cst”

std:: atomic

std::atomic

std::atomic

。。。。。。

1 CPU 、缓存和主存

第三级缓存( L3 Cache )多核共享:

C和C++中的volatile、内存屏障和CPU缓存一致性协议MESI

C和C++中的volatile、内存屏障和CPU缓存一致性协议MESI

2 SMP 对称多处理器结构

多个 CPU 对称工作没有区别,无主次或从属关系,平等地访问内存、外设和一个操作系统,共享全部资源,如总线、内存和 I/O 系统等,因此也被称为一致存储器访问结构( UMA Uniform Memory Access )。

其它的架构有:

1) NUMA Non-Uniform Memory Access ,非统一内存访问),基本特征是将 CPU 分成多个模型,每个模型多个 CPU 组成,具有独立的本地内存和 I/O 槽口等;

2) MPP Massive Parallel Processing ,海量并行处理结构),基本特征是由多个 SMP 服务器(每个 SMP 服务器称节点)通过节点互联网络连接而成,每个节点只访问自己的本地资源(内存、存储等),是一种完全无共享( Share Nothing )结构。

C和C++中的volatile、内存屏障和CPU缓存一致性协议MESI

3 :在线 C++ 编译器

1) https://www.tutorialspoint.com/compile_cpp_online.php

2) https://www.jdoodlecom/online-compiler-c++

3) http://coliru.stacked-crooked.com/

4) https://www.onlinegdb.com/online_c++_compiler

5) https://ideone.com/

6) http://cpp.sh/

7) https://www.cppbuzz.com/compiler/online-c++-compiler

8) https://www.codechef.com/ide/

9) https://repl.it/repls/ZestyNaturalMatch

10) https://www.codiva.io

11) http://codepad.org/

12) https://cppinsights.io/

13) https://tio.run/#cpp-gcc

14) http://www.compileonline.com/

聚合了各种语言在线编译。

15) https://rextester.com/l/cpp_online_compiler_gcc

还支持其它众多语言在线编译。

4 :资源链接

1) C++ 标准委员会( The C++ Standards Committee

http://www.open-std.org/jtc1/sc22/wg21/

2)  标准 C++ 基金会

https://isocpp.org

3) C++ 之父

http://www.stroustrup.com/

4) Linux 内核关于 volatile 的说明

https://www.kernel.org/doc/html/latest/process/volatile-considered-harmful.html

5) Intel 内存模型( Intel Memory Model

https://en.wikipedia.org/wiki/Intel_Memory_Model

6) Intel TSO 内存模型

https://www.cl.cam.ac.uk/~pes20/weakmemory/x86tso-paper.tphols.pdf A Better x86 Memory Model: x86-TSO

http://homepages.inf.ed.ac.uk/vnagaraj/papers/hpca14.pdf TSO-CC: Consistency directed cache coherence for TSO

7) Sequential Consistency &TSO

https://www.cis.upenn.edu/~devietti/classes/cis601-spring2016/sc_tso.pdf

8) Write buffer

https://en.wikipedia.org/wiki/Write_buffer

9) x86-64 IA-32 开发手册

https://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.html IA-32 Intel Architecture 32-bit ,即 32 x86

10)  编译器屏障( Compiler Barriers

https://www.oracle.com/technetwork/server-storage/solarisstudio/documentation/oss-compiler-barriers-176055.pdf

11) C ++ 11 memory_order_consume 的作用

https://preshing.com/20140709/the-purpose-of-memory_order_consume-in-cpp11/

12) MESI (多核 CPU 缓存一致性协议)

https://en.wikipedia.org/wiki/MESI_protocol

13) MESIF (多核 CPU 缓存一致性协议)

https://en.wikipedia.org/wiki/MESIF_protocol

M

修改( Modified

Cache line (缓存行)有效,数据被修改( dirty )了,和主存中的数据不一致,数据只存在于本 Cache

E

独占互斥( Exclusive

Cache line 只被缓存在该 CPU 的缓存中,它是未被修改过的( clean ),与主存中数据一致

S

共享( Shared

Cache line 有效,数据和内存中的数据一致,数据存在于很多 Cache

I

无效( Invalid

Cache line 无效,可能有其它 CPU 修改了该 Cache line

F

转发( Forward

Intel 提出来的,意思是一个 CPU 修改数据后,直接针修改的结果转发给其它 CPU


以上所述就是小编给大家介绍的《C和C++中的volatile、内存屏障和CPU缓存一致性协议MESI》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

程序员修炼之道

程序员修炼之道

[美]享特 / 人民邮电出版社 / 2007-12 / 49.00元

《程序员修炼之道》适合各层次软件开发人员阅读,也适合高等院校计算机专业学生和教师阅读。一起来看看 《程序员修炼之道》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

SHA 加密
SHA 加密

SHA 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器