為什麼不應該使用“volatile”型別類¶
C 語言程式設計師通常將 volatile 理解為變數可以在當前執行執行緒之外被修改;因此,當使用共享資料結構時,他們有時會嘗試在核心程式碼中使用它。換句話說,他們習慣於將 volatile 型別視為一種簡單的原子變數,但事實並非如此。在核心程式碼中使用 volatile 幾乎總是不正確的;本文件將解釋原因。
理解 volatile 的關鍵點在於,它的目的是抑制最佳化,而這幾乎從不是人們真正想要做的。在核心中,必須保護共享資料結構免受不必要的併發訪問,這是一項截然不同的任務。防止不必要併發的過程也將以更有效的方式避免幾乎所有與最佳化相關的問題。
與 volatile 類似,使資料併發訪問安全的核心原語(自旋鎖、互斥鎖、記憶體屏障等)旨在防止不必要的最佳化。如果它們被正確使用,就沒有必要再使用 volatile。如果仍然需要 volatile,那麼程式碼中幾乎肯定存在錯誤。在編寫正確的核心程式碼中,volatile 只會減慢速度。
考慮一個典型的核心程式碼塊
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/O 暫存器而設計的。在核心中,暫存器訪問也應該受到鎖的保護,但人們也不希望編譯器在臨界區內“最佳化”暫存器訪問。然而,在核心內部,I/O 記憶體訪問總是透過訪問器函式完成的;透過指標直接訪問 I/O 記憶體是不受鼓勵的,並且並非在所有架構上都有效。這些訪問器被編寫成可以防止不必要的最佳化,所以,再次強調,volatile 是不必要的。
另一種可能傾向於使用 volatile 的情況是處理器在忙等待某個變數的值。執行忙等待的正確方法是
while (my_variable != what_i_want)
cpu_relax();
`cpu_relax()` 呼叫可以降低 CPU 功耗或讓步給超執行緒的兄弟處理器;它也恰好充當編譯器屏障,因此,再次強調,volatile 是不必要的。當然,忙等待通常從一開始就是一種反社會行為。
在核心中,仍然存在一些極少數情況下 volatile 是有意義的
上述訪問器函式可能在直接 I/O 記憶體訪問有效的架構上使用 volatile。本質上,每次訪問器呼叫都成為其自身的一個小臨界區,並確保訪問按程式設計師預期的方式發生。
改變記憶體但沒有其他可見副作用的內聯彙編程式碼有被 GCC 刪除的風險。在 `asm` 語句中新增 volatile 關鍵字將防止這種刪除。
`jiffies` 變數的特殊之處在於它每次被引用時都可以有不同的值,但無需任何特殊鎖定即可讀取。因此 `jiffies` 可以是 volatile,但強烈不鼓勵新增此型別的其他變數。在此方面,`jiffies` 被認為是“愚蠢的遺留問題”(Linus 的原話);修復它將得不償失。
指向可能由 I/O 裝置修改的連貫記憶體中資料結構的指標,有時可以合理地是 volatile。網路介面卡使用的環形緩衝區就是一個例子,其中介面卡更改指標以指示哪些描述符已處理。
對於大多數程式碼,上述使用 volatile 的理由都不適用。因此,使用 volatile 很可能被視為一個 bug,並將導致程式碼受到額外的審查。傾向於使用 volatile 的開發人員應該退一步思考他們真正想要實現什麼。
移除 volatile 變數的補丁通常是受歡迎的——只要它們附帶的理由表明併發問題已得到充分考慮。
參考文獻¶
鳴謝¶
初始動議和研究:Randy Dunlap
撰寫:Jonathan Corbet
改進建議來自:Satyam Sharma, Johannes Stezenbach, Jesper Juhl, Heikki Orsila, H. Peter Anvin, Philipp Hahn 和 Stefan Richter。