內存屏障

內存屏障的分類:

  1. 編譯器引起的內存屏障

  2. 緩存引起的內存屏障

  3. 亂序執行引起的內存屏障

1、編譯器引起的內存屏障:

我們都知道,從寄存器裏面取一個數要比從內存中取快的多,所以有時候編譯器爲了編譯出優化度更高的程序,就會把一些常用變量放到寄存器中,下次使用該變量的時候就直接從寄存器中取,而不再訪問內存,這就出現了問題,當其他線程把內存中的值改變了怎麼辦?也許你會想,編譯器怎麼會那麼笨,犯這種低級錯誤呢!是的,編譯器沒你想象的那麼聰明!讓我們看下面的代碼:(代碼摘自《獨闢蹊徑品內核》

intflag=0;

voidwait(){

while(flag ==0)

sleep(1000);

......

}

voidwakeup(){

flag=1;

}

這段代碼表示一個線程在循環等待另一個線程修改flagGcc等編譯器在編譯的時候發現,sleep()不會修改flag的值,所以,爲了提高效率,它就會把某個寄存器分配給flag,於是編譯後就生成了這樣的僞彙編代碼:

voidwait(){

movl flag,%eax;

while(%eax==0)

sleep(1000);

}

這時,當wakeup函數修改了flag的值,wait函數還在傻乎乎的讀寄存器的值而不知道其實flag已經改變了,線程就會死循環下去。由此可見,編譯器的優化帶來了相反的效果!

但是,你又不能說是讓編譯器放棄這種優化,因爲在很多場合下,這種優化帶來的性能是十分可觀的!那我們該怎麼辦呢?有沒有什麼辦法可以避免這種情況?答案必須是肯定的,我們可以使用關鍵字volatile來避免這種情況

volatileintflag =0;

這樣,我們就能避免編譯器把某個寄存器分配給flag了。

好,上面所描述這些,就叫做“編譯器優化引起的內存屏障”,是不是懂了點什麼?再回去看看概念?

2、緩存引起的內存屏障

好,既然寄存器能夠引起這樣的問題,那麼緩存呢?我們都知道,CPU會把數據取到一個叫做cache的地方,然後下次取的時候直接訪問cache,寫入的時候,也先將值寫入cache

那麼,先讓我們考慮,在單核的情況下會不會出現問題呢?先想一下,單核情況下,除了CPU還會有什麼會修改內存?對了,是外部設備的DMA!那麼,DMA修改內存,會不會引起內存屏障的問題呢?答案是,在現在的體系結構中,不會。

當外部設備的DMA操作結束的時候,會有一種機制保證CPU知道他對應的緩存行已經失效了;而當CPU發動DMA操作時,在想外部設備發送啓動命令前,需要把對應cache中的內容寫回內存。在大多數RISC的架構中,這種機制是通過一寫個特殊指令來實現的。在X86上,採用一種叫做總線監測技術的方法來實現。就是CPU和外部設備訪問內存的時候都需要經過總線的仲裁,有一個專門的硬件模塊用於記錄cache中的內存區域,當外部設備對內存寫入的時候,就通過這個硬件來判斷下改內存區域是否在cache中,然後再進行相應的操作。

那麼,什麼時候才能產生cache引起的內存屏障呢?CPU是的,在多CPU的系統裏面,每個CPU都有自己的cache,當同一個內存區域同時存在於兩個CPUcache中時,CPU1改變了自己cache中的值,但是CPU2卻仍然在自己的cache中讀取那個舊值,這種結果是不是很杯具呢?因爲沒有訪存操作,總線也是沒有辦法監測的,這時候怎麼辦?

對阿,怎麼辦呢?我們需要在CPU2讀取操作之前使自己的cache失效,x86下,很多指令能做到這點,lock前綴的指令,cpuid,iret。內核中使用了一些函數來完成這個功能:mb(),rmb(), wmb()。用的也是以上那些指令,感興趣可以去看下內核代碼。

3、亂序執行引起的內存屏障:

我們都知道,超標量處理器越來越流行,連龍芯都是四發射的。超標量實際上就是一個CPU擁有多條獨立的流水線,一次可以發射多條指令,因此,很多允許指令的亂序執行,具體怎麼個亂序方法,可以去看體系結構方面的書,這裏只說內存屏障。

指令亂序執行了,就會出現問題,假設指令1給某個內存賦值,指令2從該內存取值用來運算。如果他們兩個顛倒了,指令2先從內存中取值運算,是不是就錯了?

對於這種情況,x86上專門提供了lfencesfence,mfence指令來停止流水線:

lfence:停止相關流水線,知道lfence之前對內存進行的讀取操作指令全部完成

sfence:停止相關流水線,知道lfence之前對內存進行的寫入操作指令全部完成

mfence:停止相關流水線,知道lfence之前對內存進行的讀寫操作指令全部完成


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章