C和C++中的volatile、內存屏障和CPU緩存一致性協議MESI


目錄

1. 前言2

2. 結論2

3. volatile應用場景3

4. 內存屏障(Memory Barrier4

5. setjmplongjmp4

1) 結果1(非優化編譯:g++ -g -o x x.cpp -O0 5

2) 結果2(優化編譯:g++ -g -o x x.cpp -O2 6

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

7. x86-TSO7

8. C++標准庫對內存順的支持7

1) 頭文件<stdatomic.h> 7

2) 頭文件<atomic> 8

1CPU、緩存和主存8

第三級緩存(L3 Cache)多核共享: 8

2SMP對稱多處理器結構9

3:在線C++編譯器9

4:資源鏈接10

1) C++標准委員會(The C++ Standards Committee 10

2) 標准C++基金會 10

3) C++之父 10

4) Linux內核關於volatile的說明 10

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

6) Intel TSO內存模型 10

7) Sequential Consistency &TSO 10

8) Write buffer 10

9) x86-64IA-32開發手冊 10

10) 編譯器屏障(Compiler Barriers 10

11) C ++ 11memory_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)怎么說的(官方鏈接):

 

2. 結論

1) 與平台無關的多線程程序,volatile幾乎無用(JavaC#中的volatile除外);

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

3) volatile不保證執行順序;

4) volatile不提供內存屏障(Memory Barrier)和內存柵欄(Memory Fence);

5) 多核環境中內存的可見性和CPU執行順序不能通過volatile來保障,而是依賴於CPU內存屏障。

注:volatile誕生於單CPU核心時代,為保持兼容,一直只是針對編譯器的,對CPU無影響。

 

volatileC/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<bool>替代:

https://github.com/eyjian/libmooon/blob/master/include/mooon/sys/atomic.h,實際上Linux內核源代碼都帶有這些基礎設施。

3. volatile應用場景

1) 信號處理程序;

2) 與硬件打交道(嵌入式開發用得多);

3) 和setjmplongjmp配合(請參見: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. setjmplongjmp

C/C++中,goto關鍵詞只能函數內的局部跳轉,函數間的跳轉需要使用setjmplongjmp,這也是有些協程庫基於setjmplongjmp實現的原因。

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 <stdio.h>      /* printf, scanf */

#include <stdlib.h>     /* exit */

#include <setjmp.h>     /* 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) 順序一致性模型(SCSequential Consistency,所有讀取和所有寫入都是有序的);

2) 寬松一致性模型(RCRelaxed Consistency,允許某些可以重排序)ARMPOWER屬於這類;

3) 弱一致性模型(WCWeak Consistency,讀取和寫入任意重新排序,僅受顯式內存屏障限制);

4) 完全存儲排序(TSOTotal Store Ordering),SPARCx86屬於這種類型,只有“store load”一種情況會發生重排序,其它情況和SC模型一樣。

7. x86-TSO

x86-TSOIntel推出的一種CPU內存一致性模型,特點是只有“Store Load”一種情況會重排序,也就是“Load”可以排(亂序)在“Store”的前面,因此不需要“Load Load”、“Store Store”和“Load Store”這三種屏障。

1) “Store Load”屏障的作用是:確保“前者刷入內存”的數據對“后者加載數據”是可見的;

2) “Load Load”屏障的作用是:確保“前者裝載數據”先於“后者裝載指令”;

3) “Store Store”屏障的作用是:確保“前者數據”先於“后者數據”刷入內存,且“前者刷入內存的數據”對“后者是可見的”;

4) “Load Store”屏障的作用是:確保“前者裝載數據”先於“后者刷新數據到內存”。

 

8. C++標准庫對內存順的支持

1) 頭文件<stdatomic.h>

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) 頭文件<atomic>

// 默認內存順類型為“memory_order_seq_cst”

std::atomic<bool>

std::atomic<int32_t>

std::atomic<int64_t>

。。。。。。

1CPU、緩存和主存

第三級緩存(L3 Cache)多核共享:

 

 

2SMP對稱多處理器結構

多個CPU對稱工作沒有區別,無主次或從屬關系,平等地訪問內存、外設和一個操作系統,共享全部資源,如總線、內存和I/O系統等,因此也被稱為一致存儲器訪問結構(UMA Uniform Memory Access)。

其它的架構有:

1) NUMANon-Uniform Memory Access,非統一內存訪問),基本特征是將CPU分成多個模型,每個模型多個CPU組成,具有獨立的本地內存和I/O槽口等;

2) MPPMassive Parallel Processing,海量並行處理結構),基本特征是由多個SMP服務器(每個SMP服務器稱節點)通過節點互聯網絡連接而成,每個節點只訪問自己的本地資源(內存、存儲等),是一種完全無共享(Share Nothing)結構。

 

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.pdfA Better x86 Memory Model: x86-TSO

http://homepages.inf.ed.ac.uk/vnagaraj/papers/hpca14.pdfTSO-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-64IA-32開發手冊

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

10) 編譯器屏障(Compiler Barriers

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

11) C ++ 11memory_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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM