一、什么是CPU緩存
1.1 CPU緩存的來歷
眾所周知,CPU是計算機的大腦,它負責執行程序的指令,而內存負責存數據, 包括程序自身的數據。在很多年前,CPU的頻率與內存總線的頻率在同一層面上。內存的訪問速度僅比寄存器慢一些。但是,這一局面在上世紀90年代被打破了。CPU的頻率大大提升,但內存總線的頻率與內存芯片的性能卻沒有得到成比例的提升。並不是因為造不出更快的內存,只是因為太貴了。內存如果要達到目前CPU那樣的速度,那么它的造價恐怕要貴上好幾個數量級。所以,CPU的運算速度要比內存讀寫速度快很多,這樣會使CPU花費很長的時間等待數據的到來或把數據寫入到內存中。所以,為了解決CPU運算速度與內存讀寫速度不匹配的矛盾,就出現了CPU緩存。
2. CPU緩存的概念
CPU緩存是位於CPU與內存之間的臨時數據交換器,它的容量比內存小的多但是交換速度卻比內存要快得多。CPU緩存一般直接跟CPU芯片集成或位於主板總線互連的獨立芯片上。
為了簡化與內存之間的通信,高速緩存控制器是針對數據塊,而不是字節進行操作的。高速緩存其實就是一組稱之為緩存行(Cache Line)的固定大小的數據塊組成的,典型的一行是64
字節。
3. CPU緩存的意義
CPU往往需要重復處理相同的數據、重復執行相同的指令,如果這部分數據、指令CPU能在CPU緩存中找到,CPU就不需要從內存或硬盤中再讀取數據、指令,從而減少了整機的響應時間。在CPU訪問存儲設備時,無論是存取數據抑或存取指令,都趨於聚集在一片連續的區域中,這就被稱為局部性原理。所以,緩存的意義滿足以下兩種局部性原理:
- 時間局部性(Temporal Locality):如果一個信息項正在被訪問,那么在近期它很可能還會被再次訪問。
- 空間局部性(Spatial Locality):如果一個存儲器的位置被引用,那么將來他附近的位置也會被引用。
二、CPU的三級緩存
2.1 CPU的三級緩存
隨着多核CPU的發展,CPU緩存通常分成了三個級別:L1
,L2
,L3
。級別越小越接近CPU,所以速度也更快,同時也代表着容量越小。L1 是最接近CPU的, 它容量最小(例如:32K
),速度最快,每個核上有兩個 L1 緩存, 一個用於存數據的 L1d Cache(Data Cache),一個用於存指令的 L1i Cache(Instruction Cache)。L2 緩存 更大一些(例如:256K
),速度要慢一些, 一般情況下每個核上都有一個獨立的L2 緩存; L3 緩存是三級緩存中最大的一級(例如3MB),同時也是最慢的一級, 在同一個CPU插槽之間的核共享一個 L3 緩存。結構如圖2.1:
2.1 CPU-緩存-主內存圖示
下面是三級緩存的處理速度參考表:
從CPU到 | 大約需要的CPU周期 | 大約需要的時間(單位ns) |
---|---|---|
寄存器 | 1 cycle | |
L1 Cache | ~3-4 cycles | ~0.5-1 ns |
L2 Cache | ~10-20 cycles | ~3-7 ns |
L3 Cache | ~40-45 cycles | ~15 ns |
跨槽傳輸 | ~20 ns | |
內存 | ~120-240 cycles | ~60-120ns |
就像數據庫緩存一樣,獲取數據時首先會在最快的緩存中找數據,如果緩存沒有命中(Cache miss) 則往下一級找, 直到三級緩存都找不到時,那只有向內存要數據了。一次次地未命中,代表取數據消耗的時間越長。
2.2 帶有高速緩存的CPU執行計算的流程
- 程序以及數據被加載到主內存
- 指令和數據被加載到CPU的高速緩存
- CPU執行指令,把結果寫到高速緩存
- 高速緩存中的數據寫回主內存
三、多核CPU多級緩存一致性協議MESI
多核CPU的情況下有多個一級緩存,如何保證緩存內部數據的一致,不讓系統數據混亂。這里就引出了一個一致性的協議MESI。
3.1 MESI協議緩存狀態
緩存行(Cache line):緩存存儲數據的單元。
MESI 是指4中狀態的首字母。每個Cache line有4個狀態,可用2個bit表示,它們分別是:
狀態 | 描述 | 監聽任務 |
---|---|---|
M 修改 (Modified) | 該Cache line有效,數據被修改了,和內存中的數據不一致,數據只存在於本Cache中。 | 緩存行必須時刻監聽所有試圖讀該緩存行相對就主存的操作,這種操作必須在緩存將該緩存行寫回主存並將狀態變成S(共享)狀態之前被延遲執行。 |
E 獨享、互斥 (Exclusive) | 該Cache line有效,數據和內存中的數據一致,數據只存在於本Cache中。 | 緩存行也必須監聽其它緩存讀主存中該緩存行的操作,一旦有這種操作,該緩存行需要變成S(共享)狀態。 |
S 共享 (Shared) | 該Cache line有效,數據和內存中的數據一致,數據存在於很多Cache中。 | 緩存行也必須監聽其它緩存使該緩存行無效或者獨享該緩存行的請求,並將該緩存行變成無效(Invalid)。 |
I 無效 (Invalid) | 該Cache line無效。 | 無 |
注意:
對於M和E狀態而言總是精確的,他們在和該緩存行的真正狀態是一致的,而S狀態可能是非一致的。如果一個緩存將處於S狀態的緩存行作廢了,而另一個緩存實際上可能已經獨享了該緩存行,但是該緩存卻不會將該緩存行升遷為E狀態,這是因為其它緩存不會廣播他們作廢掉該緩存行的通知,同樣由於緩存並沒有保存該緩存行的copy的數量,因此(即使有這種通知)也沒有辦法確定自己是否已經獨享了該緩存行。
從上面的意義看來E狀態是一種投機性的優化:如果一個CPU想修改一個處於S狀態的緩存行,總線事務需要將所有該緩存行的copy變成invalid狀態,而修改E狀態的緩存不需要使用總線事務。
3.2 MESI狀態轉換
3.2.1 觸發事件
觸發事件 | 描述 |
---|---|
本地讀取(Local read) | 本地cache讀取本地cache數據 |
本地寫入(Local write) | 本地cache寫入本地cache數據 |
遠端讀取(Remote read) | 其他cache讀取本地cache數據 |
遠端寫入(Remote write) | 其他cache寫入本地cache數據 |
3.2.2 cache分類
前提:所有的cache共同緩存了主內存中的某一條數據。
- 本地cache:指當前cpu的cache。
- 觸發cache:觸發讀寫事件的cache。
- 其他cache:指既除了以上兩種之外的cache。
注意:本地的事件觸發本地cache和觸發cache為相同。
3.2.3 上圖的切換解釋
狀態 | 觸發本地讀取 | 觸發本地寫入 | 觸發遠端讀取 | 觸發遠端寫入 |
---|---|---|---|---|
M狀態(修改) | 本地cache:M 觸發cache:M 其他cache:I |
本地cache:M 觸發cache:M 其他cache:I |
本地cache:M→E→S 觸發cache:I→S 其他cache:I→S 同步主內存后修改為E獨享,同步觸發、其他cache后本地、觸發、其他cache修改為S共享 |
本地cache:M→E→S→I 觸發cache:I→S→E→M 其他cache:I→S→I 同步和讀取一樣,同步完成后觸發cache改為M,本地、其他cache改為I |
E狀態(獨享) | 本地cache:E 觸發cache:E 其他cache:I |
本地cache:E→M 觸發cache:E→M 其他cache:I 本地cache變更為M,其他cache狀態應當是I(無效) |
本地cache:E→S 觸發cache:I→S 其他cache:I→S 當其他cache要讀取該數據時,其他、觸發、本地cache都被設置為S(共享) |
本地cache:E→S→I 觸發cache:I→S→E→M 其他cache:I→S→I 當觸發cache修改本地cache獨享數據時時,將本地、觸發、其他cache修改為S共享.然后觸發cache修改為獨享,其他、本地cache修改為I(無效),觸發cache再修改為M |
S狀態(共享) | 本地cache:S 觸發cache:S 其他cache:S |
本地cache:S→E→M 觸發cache:S→E→M 其他cache:S→I 當本地cache修改時,將本地cache修改為E,其他cache修改為I,然后再將本地cache為M狀態 |
本地cache:S 觸發cache:S 其他cache:S |
本地cache:S→I 觸發cache:S→E→M 其他cache:S→I 當觸發cache要修改本地共享數據時,觸發cache修改為E(獨享),本地、其他cache修改為I(無效),觸發cache再次修改為M(修改) |
I狀態(無效) | 本地cache:I→S或者I→E 觸發cache:I→S或者I →E 其他cache:E、M、I→S、I 本地、觸發cache將從I無效修改為S共享或者E獨享,其他cache將從E、M、I 變為S或者I |
本地cache:I→S→E→M 觸發cache:I→S→E→M 其他cache:M、E、S→S→I |
既然是本cache是I,其他cache操作與它無關 | 既然是本cache是I,其他cache操作與它無關 |
下圖示意了,當一個cache line的調整的狀態的時候,另外一個cache line 需要調整的狀態。
M | E | S | I | |
---|---|---|---|---|
M | × | × | × | √ |
E | × | × | × | √ |
S | × | × | √ | √ |
I | √ | √ | √ | √ |
舉個例子來說:
假設cache 1 中有一個變量x = 0的cache line 處於S狀態(共享)。
那么其他擁有x變量的cache 2、cache 3等x的cache line調整為S狀態(共享)或者調整為 I 狀態(無效)。
3.3 多核緩存協同操作
假設有三個CPU A、B、C,對應三個緩存分別是cache a、b、 c。在主內存中定義了x的引用值為0。
3.3.1 單核讀取
那么執行流程是:
CPU A發出了一條指令,從主內存中讀取x。
從主內存通過bus讀取到緩存中(遠端讀取Remote read),這是該Cache line修改為E狀態(獨享)。
3.3.2 雙核讀取
那么執行流程是:
CPU A發出了一條指令,從主內存中讀取x。
CPU A從主內存通過bus讀取到 cache a中並將該cache line 設置為E狀態。
CPU B發出了一條指令,從主內存中讀取x。
CPU B試圖從主內存中讀取x時,CPU A檢測到了地址沖突。這時CPU A對相關數據做出響應。此時x 存儲於cache a和cache b中,x在chche a和cache b中都被設置為S狀態(共享)。
3.3.3 修改數據
那么執行流程是:
CPU A 計算完成后發指令需要修改x.
CPU A 將x設置為M狀態(修改)並通知緩存了x的CPU B, CPU B將本地cache b中的x設置為I狀態(無效)
CPU A 對x進行賦值。
3.3.4 同步數據
那么執行流程是:
CPU B 發出了要讀取x的指令。
CPU B 通知CPU A,CPU A將修改后的數據同步到主內存時cache a 修改為E(獨享)
CPU A同步CPU B的x,將cache a和同步后cache b中的x設置為S狀態(共享)。
3.3.5 一個CPU要修改一個處於shared狀態的變量
1.本地緩存行將會通過寄存器控制器向遠程擁有相同緩存行的寄存器發送一個RFO請求(Request For Owner),告訴其他CPU里面的緩存把緩存里面的值為valid狀態,然后待收到各個緩存的(valid ack)已經完成無效狀態修改的回應之后,
2.再把自己的狀態改為Exclusive,之后再進行修改。
3.修改后再改為Modified狀態,數據寫入緩存行。
上面這幾步大家可以看到第一步的時候,CPU需要在等待所有的valid ack之后才會進行下面的操作。這部分就會讓CPU產生一定的阻塞,無法充分利用CPU。這個時候就印出來了存儲緩沖區 storeBuffer。
四、MESI優化和他們引入的問題
緩存的一致性消息傳遞是要時間的,這就使其切換時會產生延遲。當一個緩存被切換狀態時其他緩存收到消息完成各自的切換並且發出回應消息這么一長串的時間中CPU都會等待所有緩存響應完成。可能出現的阻塞都會導致各種各樣的性能問題和穩定性問題。
4.1 CPU切換狀態阻塞解決-存儲緩存(Store Bufferes)
比如你需要修改本地緩存中的一條信息,那么你必須將I(無效)狀態通知到其他擁有該緩存數據的CPU緩存中,並且等待確認。等待確認的過程會阻塞處理器,這會降低處理器的性能。因為這個等待遠遠比一個指令的執行時間長的多。
4.2 Store Bufferes
為了避免這種CPU運算能力的浪費,Store Bufferes被引入使用。存儲緩沖區的作用就是修改一個變量的時候,直接執行修改的操作不直接鎮對緩存行,而是針對一個叫制作storeBuffer的位置來操作的。這樣CPU在執行修改操作的時候,直接把數據寫入到storeBuffer里面,並發出廣播告知其他CPU,你們的緩存里面需要變為validate狀態,然后去執行其他的操作,等接受到validate ack的時候才會回來把緩沖區里面的值寫入到緩存行里面。但是這么做有兩個風險。
4.3 Store Bufferes(寫緩存區或存儲緩存器)的風險
寫操作:
- 如果相應的緩存條目狀態為 E、M,則直接寫入
- 如果相應的緩存條目狀態為 S, 處理器會將寫操作相關信息存入寫緩沖器,並發送Invalidate消息。(不再等待響應消息)
- 如果相應的緩存條目狀態為 I,將寫操作相關信息存入寫緩沖器,並發送Read Invalidate消息。(不再等待響應消息)
當處理器將寫操作寫入寫緩沖器后,則認為寫操作已經完成。而實際上,當處理器收到其他所有處理器回應的Read Response、Invalidate Acknowledge消息后,處理器才會將寫緩沖器中對應的寫操作寫入相應的緩存行,這個時候,寫操作才算真正完成。
寫緩沖器讓處理器在執行寫操作時不需要再額外的等待,減少了寫操作的等待,提高了處理器的指令執行效率。
讀操作:
引入寫緩存器后,處理器讀取數據時,由於該數據的更新結果可能仍然停留在寫緩沖器中,所以處理器會先從寫緩沖器中找尋數據,沒有找到時,才從高速緩存中找。這種處理器直接從寫緩沖器中讀取數據的技術被稱為:存儲轉發。
1 value = 3; 2
3 void exeToCPUA(){ 4 value = 10; 5 isFinsh = true; 6 } 7 void exeToCPUB(){ 8 if(isFinsh){ 9 //value一定等於10?!
10 assert value == 10; 11 } 12 }
試想一下開始執行時,CPU A保存着isfinish在E(獨享)狀態,而value並沒有保存在它的緩存中。(例如,Invalid)。在這種情況下,value會比isfinish更遲地拋棄存儲緩存。完全有可能CPU B讀取finished的值為true,而value的值不等於10。也會就是存儲緩存雖然可以解決等待阻塞導致CPU性能浪費的問題,但是同樣引入了新的問題---重排序,也就是isFinsh的賦值在value賦值之前。
4.4 重排序
這種在可識別的行為中發生的變化稱為重排序(reordings)。注意,這不意味着你的指令的位置被惡意(或者好意)地更改。它只是意味着其他的CPU會讀到跟程序中寫入的順序不一樣的結果。
4.5 失效隊列
存儲緩存(Store Buffers)並不是無窮大的,所以處理器有時需要等待失效確認的返回。這兩個操作都會使得性能大幅降低。為了應付這種情況,引入了失效隊列。它們的約定如下:
- 對於所有的收到的Invalidate請求,Invalidate Acknowlege消息必須立刻發送
- Invalidate並不真正執行,而是被放在一個特殊的隊列中,在方便的時候才會去執行。
- 處理器不會發送任何消息給所處理的緩存條目,直到它處理Invalidate。
也就是失效隊列的作用是CPU接收到validate廣播的時候馬上返回給對方validate ack響應。等當前的操作執行完再回來真正的把緩存里面的值標識為validate狀態。
但是失效隊列造成的問題就是在CPU已經給對方應答的時候自己本身還沒有去把這個值validate掉,也就是可見性問題(亂序執行),明明寫了 其他線程看不到。
4.6重排序問題和重排序的解決
通過volatile標記,可以解決編譯器層面的可見性與重排序問題。而內存屏障則解決了硬件層面的可見性與重排序問題
4.6.1 volatile
4.6.1.1 volatile介紹
volatile提醒編譯器它后面所定義的變量隨時都有可能改變,因此編譯后的程序每次需要存儲或讀取這個變量的時候,都會直接從變量地址中讀取數據。如果沒有volatile關鍵字,則編譯器可能優化讀取和存儲,可能暫時使用寄存器中的值,如果這個變量由別的程序更新了的話,將出現不一致的現象。下面舉例說明。在DSP開發中,經常需要等待某個事件的觸發,所以經常會寫出這樣的程序:
這段程序等待內存變量flag的值變為1(懷疑此處是0,有點疑問,)之后才運行do2()。變量flag的值由別的程序更改,這個程序可能是某個硬件中斷服務程序。例如:如果某個按鈕按下的話,就會對DSP產生中斷,在按鍵中斷程序中修改flag為1,這樣上面的程序就能夠得以繼續運行。但是,編譯器並不知道flag的值會被別的程序修改,因此在它進行優化的時候,可能會把flag的值先讀入某個寄存器,然后等待那個寄存器變為1。如果不幸進行了這樣的優化,那么while循環就變成了死循環,因為寄存器的內容不可能被中斷服務程序修改。為了讓程序每次都讀取真正flag變量的值,就需要定義為如下形式:
需要注意的是,沒有volatile也可能能正常運行,但是可能修改了編譯器的優化級別之后就又不能正常運行了。因此經常會出現debug版本正常,但是release版本卻不能正常的問題。所以為了安全起見,只要是等待別的程序修改某個變量的話,就加上volatile關鍵字。
volatile的本意是“易變的”,由於訪問寄存器的速度要快過RAM,所以編譯器一般都會作減少存取外部RAM的優化。比如:
程序的本意是希望ISR_2中斷產生時,在main當中調用do_something函數,但是,由於編譯器判斷在main函數里面沒有修改過i,因此可能只執行一次對從i到某寄存器的讀操作,然后每次if判斷都只使用這個寄存器里面的“i副本”,導致do_something永遠也不會被調用。如果變量加上volatile修飾,則編譯器保證對此變量的讀寫操作都不會被優化(肯定執行)。此例中i也應該如此說明。
一般說來,volatile用在如下的幾個地方:
中斷服務程序中修改的供其它程序檢測的變量需要加volatile;
2、多任務環境下各任務間共享的標志應該加volatile;
3、存儲器映射的硬件寄存器通常也要加volatile說明,因為每次對它的讀寫都可能由不同意義;
另外,以上這幾種情況經常還要同時考慮數據的完整性(相互關聯的幾個標志讀了一半被打斷了重寫),在1中可以通過關中斷來實現,2中可以禁止任務調度,3中則只能依靠硬件的良好設計了。
4.6.1.2 volatile 的含義
volatile總是與優化有關,編譯器有一種技術叫做數據流分析,分析程序中的變量在哪里賦值、在哪里使用、在哪里失效,分析結果可以用於常量合並,常量傳播等優化,進一步可以死代碼消除。但有時這些優化不是程序所需要的,這時可以用volatile關鍵字禁止做這些優化,volatile的字面含義是易變的,它有下面的作用:
1、不會在兩個操作之間把volatile變量緩存在寄存器中。在多任務、中斷、甚至setjmp環境下,變量可能被其他的程序改變,編譯器自己無法知道,volatile就是告訴編譯器這種情況。
2、不做常量合並、常量傳播等優化,所以像下面的代碼:
if的條件不會當作無條件真。
3、對volatile變量的讀寫不會被優化掉。如果你對一個變量賦值但后面沒用到,編譯器常常可以省略那個賦值操作,然而對Memory Mapped IO的處理是不能這樣優化的。
前面有人說volatile可以保證對內存操作的原子性,這種說法不大准確,其一,x86需要LOCK前綴才能在SMP下保證原子性,其二,RISC根本不能對內存直接運算,要保證原子性得用別的方法,如atomic_inc。
對於jiffies,它已經聲明為volatile變量,我認為直接用jiffies++就可以了,沒必要用那種復雜的形式,因為那樣也不能保證原子性。
你可能不知道在Pentium及后續CPU中,下面兩組指令作用相同,但一條指令反而不如三條指令快。
4.6.1.3 編譯器優化 → C關鍵字volatile → memory破壞描述符
memory比較特殊,可能是內嵌匯編中最難懂部分。為解釋清楚它,先介紹一下編譯器的優化知識,再看C關鍵字volatile。最后去看該描述符。
1、編譯器優化介紹
內存訪問速度遠不及CPU處理速度,為提高機器整體性能,在硬件上引入硬件高速緩存Cache,加速對內存的訪問。另外在現代CPU中指令的執行並不一定嚴格按照順序執行,沒有相關性的指令可以亂序執行,以充分利用CPU的指令流水線,提高執行速度。以上是硬件級別的優化。再看軟件一級的優化:一種是在編寫代碼時由程序員優化,另一種是由編譯器進行優化。編譯器優化常用的方法有:將內存變量緩存到寄存器;調整指令順序充分利用CPU指令流水線,常見的是重新排序讀寫指令。對常規內存進行優化的時候,這些優化是透明的,而且效率很好。由編譯器優化或者硬件重新排序引起的問題的解決辦法是在從硬件(或者其他處理器)的角度看必須以特定順序執行的操作之間設置內存屏障(memory barrier),linux 提供了一個宏解決編譯器的執行順序問題。
這個函數通知編譯器插入一個內存屏障,但對硬件無效,編譯后的代碼會把當前CPU寄存器中的所有修改過的數值存入內存,需要這些數據的時候再重新從內存中讀出。
2、C語言關鍵字volatile
C語言關鍵字volatile(注意它是用來修飾變量而不是上面介紹的volatile)表明某個變量的值可能在外部被改變,因此對這些變量的存取不能緩存到寄存器,每次使用時需要重新存取。該關鍵字在多線程環境下經常使用,因為在編寫多線程的程序時,同一個變量可能被多個線程修改,而程序通過該變量同步各個線程,例如:
該線程啟動時將intSignal置為2,然后循環等待直到intSignal為1時退出。顯然intSignal的值必須在外部被改變,否則該線程不會退出。但是實際運行的時候該線程卻不會退出,即使在外部將它的值改為1,看一下對應的偽匯編代碼就明白了:
對於C編譯器來說,它並不知道這個值會被其他線程修改。自然就把它cache在寄存器里面。記住,C 編譯器是沒有線程概念的!這時候就需要用到volatile。volatile 的本意是指:這個值可能會在當前線程外部被改變。也就是說,我們要在threadFunc中的intSignal前面加上volatile關鍵字,這時候,編譯器知道該變量的值會在外部改變,因此每次訪問該變量時會重新讀取,所作的循環變為如下面偽碼所示:
3、Memory
有了上面的知識就不難理解Memory修改描述符了,Memory描述符告知GCC:
1)不要將該段內嵌匯編指令與前面的指令重新排序;也就是在執行內嵌匯編代碼之前,它前面的指令都執行完畢。
2)不要將變量緩存到寄存器,因為這段代碼可能會用到內存變量,而這些內存變量會以不可預知的方式發生改變,因此GCC插入必要的代碼先將緩存到寄存器的變量值寫回內存,如果后面又訪問這些變量,需要重新訪問內存。
如果匯編指令修改了內存,但是GCC 本身卻察覺不到,因為在輸出部分沒有描述,此時就需要在修改描述部分增加“memory”,告訴GCC 內存已經被修改,GCC 得知這個信息后,就會在這段指令之前,插入必要的指令將前面因為優化Cache 到寄存器中的變量值先寫回內存,如果以后又要使用這些變量再重新讀取。
使用“volatile”也可以達到這個目的,但是我們在每個變量前增加該關鍵字,不如使用“memory”方便。
4.6.2 內存屏障
內存屏障(Memory Barrier)與內存柵欄(Memory Fence)是同一個概念,不同的叫法。通過volatile標記,可以解決編譯器層面的可見性與重排序問題。而內存屏障則解決了硬件層面的可見性與重排序問題。
4.6.2.1 內存屏障
mb(), wmb(), mb(),可以防止硬件上的指令重排。除了編譯器,有的CPU也支持對指令進行重排來優化程序執行效率,這幾個函數就是去防止CPU去做這些事情。rmb()是讀訪問內存屏障,它保證在屏障(調用rmb()的位置處)之后的任何讀操作在執行之前,屏障之前的所有讀操作都已經完成。wmb()對應寫操作,保證在屏障(調用wmb()的位置處)之后的任何寫操作之前,屏障之前的所有寫操作都已經完成。mb()就同時包含讀和寫操作,保證在屏障(調用wmb()的位置處)之后的任何讀或寫操作之前,屏障之前的所有讀和寫操作都已經完成。
4.6.2.2 優化屏障
barrier(),防止編譯器對內存訪問的優化,類似volatile關鍵字對於訪問變量的作用。它告訴編譯器,在插入barrier()的位置處,內存中的內容都被更新了,你想讀變量、映射到內存的寄存器等內容都需要真正到內存里去讀,這樣就能保證barrier之后的讀指令不會被優化掉。
4.6.2.3 內存屏障和優化屏障
內存屏障是和硬件即CPU特性相關的,那么,如果你的CPU沒有指令重排的能力,也就沒有必要防止指令重排了。例如,一款CPU不支持寫指令重排,那么系統中的wmb()就直接被定義成了barrier()。還有SMP系統中使用的smp_rmb(), smp_wmb(), smp_mb(),它們只用於SMP系統。在單處理器上它們被定義成barrier()。
當然,防止優化后,受影響的代碼執行效率會降低,但為了保證正確性,犧牲一點性能是值得的。優化屏障的一個特定應用是內核的搶占機制。
五、參考文章
https://www.cnblogs.com/xuanbjut/p/11608991.html
https://www.cnblogs.com/yanlong300/p/8986041.html
https://blog.csdn.net/weixin_44363885/article/details/92838607
https://blog.csdn.net/jasonchen_gbd/article/details/80151265
https://blog.csdn.net/qq_30055391/article/details/84892936
https://blog.csdn.net/qq_32680371/article/details/107848269