嘗試總結memory barrier (經典)

2021-09-30 05:26:08 字數 3885 閱讀 4038

討論完了給乙個總結,有些話是別人說的,有的還是clf的網友的,為了不使文件顯得雜亂,都不具名了。

歡迎批評指正!

核心中定義的記憶體屏障原語有:

#define barrier() __asm__ __volatile__("": : :"memory")

#define mb() alternative("lock; addl $0,0(%%esp)", "mfence", x86_feature_xmm2)

#define rmb() alternative("lock; addl $0,0(%%esp)", "lfence", x86_feature_xmm2)

#ifdef config_smp

#define smp_mb() mb()

#define smp_rmb() rmb()

#define smp_wmb() wmb()

#define smp_read_barrier_depends() read_barrier_depends()

#define set_mb(var, value) do while (0)

#else

#define smp_mb() barrier()

#define smp_rmb() barrier()

#define smp_wmb() barrier()

#define smp_read_barrier_depends() do while(0)

#define set_mb(var, value) do while (0)

#endif

1). smp_***()和***()的區別

為了給其它cpu也提供相關的barrier巨集。 例如x86的rmb()是用了lfence指令,但其它cpu不能用這個指令。

2). 關於barrier()巨集,jkl大師是這麼說的:

cpu越過記憶體屏障後,將重新整理自己對儲存器的緩衝狀態。這條語句實際上不生成任何**,但可使gcc在

barrier()之後重新整理暫存器對變數的分配。

也就是說,barrier()巨集只約束gcc編譯器,不約束執行時的cpu行為。 舉例:

1 int a = 5, b = 6;

2 barrier();

3 a = b;

在line 3,gcc不會用存放b的暫存器給a賦值,而是invalidate b的cache line,重新讀記憶體中的b值,賦值給a。

3). mb() vs. rmb() vs. wmb()

rmb()不允許讀操作穿過記憶體屏障;wmb()不允許寫操作穿過屏障;而mb()二者都不允許。

看ia32上wmb()的定義:

#ifdef config_x86_oostore

#define wmb() alternative("lock;addl $0,0(%%esp)", "sfence", x86_feature_xmm);

#else

#define wmb() __asm__ __volatile__ ("": : :"memory");

#endif

intel和amd都沒有在ia32 cpu中實現亂序寫(out-of-order store),所以wmb()定義為空操作,不約束cpu行為;但

有些ia32 cpu廠商實現了ooo store,所以就有了使用sfence的那個wmb()實現。

4). 記憶體屏障的體系結構語義

4.1) 只有乙個主體(cpu或dma控制器)訪問記憶體時,無論如何也不需要barrier;但如果有兩個或更多主體訪問記憶體,且

其中有乙個在觀測另乙個,就需要barrier了。

4.2) ia32 cpu呼叫有lock字首的指令,或者如xchg這樣的指令,會導致其它的cpu也觸發一定的動作來同步自己的cache。

cpu的#lock引腳鏈結到北橋晶元(north bridge)的#lock引腳,當帶lock字首的執行執行時,北橋晶元會拉起#lock

電平,從而鎖住匯流排,直到該指令執行完畢再放開。 而匯流排加鎖會自動invalidate所有cpu對 _該指令涉及的記憶體_

的cache,因此barrier就能保證所有cpu的cache一致性。

4.3) 接著解釋。

lock字首(或cpuid、xchg等指令)使得本cpu的cache寫入了記憶體,該寫入動作也會引起別的cpu invalidate其cache。

ia32在每個cpu內部實現了snoopying(bus-watching)技術,監視著匯流排上是否發生了寫記憶體操作(由某個cpu或dma控

製器發出的),只要發生了,就invalidate相關的cache line。 因此,只要lock字首導致本cpu寫記憶體,就必將導致

所有cpu去invalidate其相關的cache line。

兩個地方可能除外:

-> 如果採用write-through策略,則根本不存在快取一致性問題(linux對全部記憶體採用write-back策略);

-> tlb也是cache,但它的一致性(至少在ia32上)不能通過snoopying技術解決,而是要傳送

invalidate_tlb_vector這個ipi給其它的cpu。

4.4) 進一步解釋,mesi協議

包括ia32的許多體系結構的cpu,為了保證快取一致性,實現了mesi協議。

m: modified,已修改

e: exclusive,排他

s: shared,共享

i: invalid,無效

ia32 的cpu實現了mesi協議來保證cache coherence。 cpu的匯流排監測單元,始終監視著匯流排上所有的記憶體寫操作,

以便隨時調整自己的cache狀態。

-> modified。 本cpu寫,則直接寫到cache,不產生匯流排事物;其它cpu寫,則不涉及本cpu的cache,其它cpu

讀,則本cpu需要把cache line中的資料提供給它,而不是讓它去讀記憶體。

-> exclusive。只有本cpu有該記憶體的cache,而且和記憶體一致。 本cpu的寫操作會導致轉到modified狀態。

-> shared。 多個cpu都對該記憶體有cache,而且內容一致。任何乙個cpu寫自己的這個cache都必須通知其它

的cpu。

-> invalid。 一旦cache line進入這個狀態,cpu讀資料就必須發出匯流排事物,從記憶體讀。

5) 考慮到dma

5.1). wirte through策略。 這種情形比較簡單。

-> 本cpu寫記憶體,是write through的,因此無論什麼時候dma讀記憶體,讀到的都是正確資料。

-> dma寫記憶體,如果dma要寫的記憶體被本cpu快取了,那麼必須invalidate這個cache line。下次cpu讀它,就

直接從記憶體讀。

5.2). write back策略。 這種情形相當複雜。

-> dma讀記憶體。被本cpu匯流排監視單元發現,而且本地cache中有modified資料,本cpu就截獲dma的記憶體讀操作,

把自己cache line中的資料返回給它。

-> dma寫記憶體。而且所寫的位置在本cpu的cache中,這又分兩種情況:

a@ cache line狀態未被cpu修改過(即cache和記憶體一致),那麼invalidate該cache line。

b@ cache line狀態已經被修改過,又分2種情況:

<1> dma寫操作會替換cpu cache line所對應的整行記憶體資料,那麼dma寫,cpu則invalidate

自己的cache line。

<2> dma寫操作只替換cache line對應的記憶體資料的一部分,那麼cpu必須捕獲dma寫操作的新

資料(即dma想把它寫入記憶體的),用來更新cache line的相關部分。

停止執行緒方法嘗試和總結

public static void main string args thread.currentthread getname innerrunnable myrunnable new innerrunnable thread thread1 new thread myrunnable,執行緒1 ...

嘗試著說,嘗試著做

成長,需要磨練 生活,需要經歷 習慣,需要堅持。心裡有什麼,說出來 想什麼,寫出來 要什麼,做出來。很多東西,如果自己不去說,不去做。就真的成為自己的東西了,成為爛在自己心裡的東西了。如果自己不知道自己想做什麼,會做什麼,就嘗試著去想,嘗試著去做,不能讓自己在混沌中屏跡。如果自己沒有自己的心聲,就嘗...

分治嘗試A

簡單的分治 平面上最近點對 description 給定平面上n個點,找出其中的一對點的距離,使得這n個點的所有點對中,該距離為所有點對中最小的。input 第一行 n 2 n 60000 接下來n行 每行兩個整數 x y,表示乙個點的行座標和列座標,中間用乙個空格隔開。output 僅一行,乙個實...