(在Kernel中) 為什麼不應該使用"volatile"類型 by Jonathan Corbet
原文出自Linux kernel source code的"\Documentation\ volatile-considered-harmful.txt"
------------------------------
C程式師通常認為volatile表示某個變數可以在當前執行的執行緒之外被改變;因此,在Kernel中用到共用資料結構時,常常會有C程式師喜歡使用volatile這類變數。換句話說,他們經常會把volatile類型看成某種簡易的atomic變數,當然它們不是。在Kernel中使用volatile幾乎總是錯誤的;本文檔將解釋為什麼這樣。
理解volatile的關鍵是知道它的目的是用來消除優化,實際上很少有人真正需要這樣的應用。在Kernel中,程式師必須防止意外的concurrent access訪問破壞共用的資料結構,這其實是一個完全不同的任務。用來防止意外concurrency的保護措施,可以更加高效的避免大多數優化相關的問題。
像volatile一樣,Kernel提供了很多原語來保證concurrent access時的資料安全(自旋鎖, 互斥量, memory barriers等等),同樣可以防止意外的優化。如果可以正確使用這些Kernel原語,那麼就沒有必要再使用volatile。如果仍然必須使用volatile,那麼幾乎可以肯定在代碼的某處有一個bug。在正確設計的Kernel代碼中,volatile能帶來的僅僅是使事情變慢。
思考一下這段典型的Kernel代碼:
spin_lock(&the_lock);
do_something_on(&shared_data);
do_something_else_with(&shared_data);
spin_unlock(&the_lock);
如果所有的代碼都遵循加鎖規則,當持有the_lock的時候,不可能意外的改變shared_data的值。任何可能訪問該資料的其他代碼都會在這個鎖上等待。自旋鎖原語跟記憶體屏障一樣—— 它們顯式的用來書寫成這樣
—— 意味著資料訪問不會跨越它們而被優化。所以本來編譯器認為它知道在shared_data裡面將有什麼,但是因為spin_lock()調用跟記憶體屏障一樣,會強制編譯器忘記它所知道的一切。那麼在訪問這些資料時不會有優化的問題。
如果shared_data被聲名為volatile,鎖操作將仍然是必須的。就算我們知道沒有其他人正在使用它,編譯器也將被阻止優化對臨界區內shared_data的訪問。在鎖有效的同時,shared_data不是volatile的。在處理共用資料的時候,適當的鎖操作可以不再需要volatile ——
並且是有潛在危害的。
volatile的存儲類型最初是為那些記憶體映射的I/Oregister而定義。在Kernel裡,register訪問也應該被鎖保護,但是人們也不希望編譯器“優化”臨界區內的register訪問。Kernel裡I/O的記憶體訪問是通過訪問函數完成的;不贊成通過指標對I/O記憶體的直接訪問,並且不是在所有體系架構上都能工作。那些訪問函數正是為了防止意外優化而寫的,因此,再說一次,volatile類型不是必需的。
另一種引起使用者可能使用volatile的情況是當處理器正忙著等待一個變數的值。正確執行一個busy wait的方法是:
while (my_variable != what_i_want)
cpu_relax();
cpu_relax()調用會降低CPU的能量消耗或者讓位於超執行緒雙處理器;它也作為記憶體屏障一樣出現,所以,再一次,volatile不是必需的。當然,busy wait一開始就是一種反常規的做法。
在Kernel中,一些稀少的情況下volatile仍然是有意義的:
1. 在一些體系架構的系統上,允許直接的I/0記憶體訪問,那麼前面提到的訪問函數可以使用volatile。基本上,每一個訪問函式呼叫它自己都是一個小的臨界區域並且保證了按照程式師期望的那樣發生訪問操作。
2. 某些會改變記憶體的內聯彙編代碼雖然沒有什麼其他明顯的附作用,但是有被GCC刪除的可能性。在彙編聲明中加上volatile關鍵字可以防止這種刪除操作。
3.
Jiffies變數是一種特殊情況,雖然每次引用它的時候都可以有不同的值,但讀jiffies變數時不需要任何特殊的加鎖保護。所以jiffies變數可以使用volatile,但是不贊成其他跟jiffies相同類型變數使用volatile。Jiffies被認為是一種"愚蠢的遺留物"(by
Linus)因為解決這個問題比保持現狀要麻煩的多。
4. 由於某些I/0設備可能會修改連續一致的記憶體,所以有時,指向連續一致記憶體的資料結構的指標需要正確的使用volatile。網路介面卡使用的環狀緩存區正是這類情形的一個例子,其中適配器用改變指針來表示哪些描述符已經處理過了。
對於大多代碼,上述幾種可以使用volatile的情況都不適用。所以,使用volatile是一種bug並且需要對這樣的代碼額外仔細檢查。那些試圖使用volatile的開發人員需要退一步想想他們真正想實現的是什麼。
非常歡迎刪除volatile變數的補丁 - 只要證明這些補丁完整的考慮了concurrency問題。
沒有留言:
張貼留言