作者:新浪微博(@NP等不等于P) 計算機學習微信公眾號(jsj_xx) 先回顧一下,。 上次講過compiler相關的優(yōu)化屏障和ACCESS_ONCE(),。對于ACCESS_ONCE()粒度更細,重在理解ONCE字眼,,compiler需要其使用次數不是(compiler優(yōu)化可能導致的)多次也不是0次,,而是1次。 2.3.1.2 cpu內存屏障 linux內核的8個內存屏障實現:(強制(MANDATORY)屏障可以用在UP系統(tǒng))
除了數據依賴屏障,,其它屏障都內含優(yōu)化屏障,。看看數據依賴屏障,,它不需要優(yōu)化屏障的配合,,因為compiler會感知這種依賴關系。顯然,,在UP系統(tǒng)中, SMP內存屏障將退化為優(yōu)化屏障(因為內存屏障本質是在協(xié)調多cpu之間的操作),。而SMP系統(tǒng)上的共享內存的(有序)訪問必須使用內存屏障或者鎖。 還有一些高級的函數,,封裝了特定的操作:(和ACCESS_ONCE()一樣,,這樣控制粒度更精細精準了) (*) set_mb(var, value) value賦值給var之后插入一個通用內存屏障。顯然,,在UP系統(tǒng),,插入的屏障退化為一個優(yōu)化屏障。 (*) smp_mb__before_atomic()/smp_mb__after_atomic(); 用于沒有返回值的原子(加/減/清bit/置bit)操作,,一般用于引入計數,,該原子操作本身不含屏障,用該函數插入一個通用內存屏障,。比如:(先宣告死亡,,然后計數減1)
這樣,就保證了先宣告死亡后改變計數的有序順序,。 2.3.1.3 mmio寫屏障 對內存映射的io空間使用mmiowb()來保證寫順序,,后文的另一個部分會講,此處略過,。 2.3.2 內核隱式內存屏障 2.3.2.1 鎖 內核中的一些鎖結構都是基于ACQUIRE/RELEASE操作,,也就是隱含含有內存屏障的,比如: (*) spin locks (*) R/W spin locks (*) mutexes (*) semaphores (*) R/W semaphores (*) RCU 回憶一下之前講的ACQUIRE/RELEASE操作,,我們知道:
可能會有這樣的順序:
或者更狠的:(ACQUIRE和RELEASE操作的是兩個不相關的臨界區(qū))
可能會有這樣的執(zhí)行順序:
總結就是,,這兩個配合使用不是一個完全意義的內存屏障,但是確實對臨界區(qū)內的代碼有保護作用,。 2.3.2.2 睡眠和喚醒 考慮下睡眠和喚醒兩個操作,,它們之間需要有屏障的保護,。 睡眠代碼:
喚醒代碼:
此時,正確的使用是:
這樣才能保證cpu1在被喚醒之后能夠感知到條件變量event_indicated的最新值,。 2.4 cpu間的鎖和屏障的關系 cpu間的鎖,,比如自旋鎖,也需要考慮順序問題,,比如:
cpu1和cpu2在各自的cpu上能夠保證各自的有序序列,,但是cpu3上看到的則是cpu1和cpu2的兩個有序序列的交叉。再看個交叉的例子:(對設備的io空間做寫操作)
此時的如果cpu1和cpu2的寫操作交叉執(zhí)行就出錯,,解決交叉的方法是使用mmiowb()寫屏障:
2.5 哪里需要使用屏障 要喚醒一個等待進程, 信號量喚醒函數有這些處理步驟: 1)讀進程的waiter->list.next,記住下一個等待進程,; 2)讀進程的waiter->task,,記住task指針; 3)清進程的waiter->task指針,;(表示該進程已經獲得該信號量,,可以理解為:清完該進程就能調度運行了) 4)調用wakeup喚醒該進程; 5)釋放waiter->task的引用計數,; 用代碼表示就是:
這里前兩個是讀操作,,第三個是寫操作,如果三者之間發(fā)生亂序:
cpu1上OOPS原因就是沒有讓兩個讀操作先于寫操作導致,,解決的方法是插入通用屏障:
2.6 內核中io屏障的作用 在操作io空間時,,需要使用適當的函數,比如mmiowb()寫屏障,。 2.7 執(zhí)行有序的最小假想模型 x86對程序執(zhí)行順序有較強約束,,但是我們也應該看到,很多其它cpu都是弱約束的: 關于我們 新浪微博(@NP等不等于P) 計算機學習微信公眾號(jsj_xx) 原創(chuàng)技術文章,,感悟計算機,,透徹理解計算機! |
|