C programmers have often taken volatile to mean that the variable could be
changed outside of the current thread of execution; as a result, they are
sometimes tempted to use it in kernel code when shared data structures are
being used. In other words, they have been known to treat volatile types
as a sort of easy atomic variable, which they are not. The use of volatile in
kernel code is almost never correct; this document describes why.
C程式員通常認為volatile表示某個變數可以在當前執行的線程之外被改變;因此,
在核心中用到共用資料結構時,常常會有C程式員喜歡使用volatile這類變數。換句話說,
他們經常會把volatile類型看成某種簡易的原子變數,當然它們不是。在核心中使用
volatile幾乎總是錯誤的;本文檔將解釋為什麼這樣。
The key point to understand with regard to volatile is that its purpose is
to suppress optimization, which is almost never what one really wants to
do. In the kernel, one must protect shared data structures against
unwanted concurrent access, which is very much a different task. The
process of protecting against unwanted concurrency will also avoid almost
all optimization-related problems in a more efficient way.
理解volatile的關鍵是知道它的目的是用來消除最佳化,實際上很少有人真正需要這樣的
應用。在核心中,程式員必須防止意外的並發訪問破壞共用的資料結構,這其實是一個
完全不同的任務。用來防止意外並發訪問的進程保護措施,可以更加高效的避免大多數
最佳化相關的問題。
Like volatile, the kernel primitives which make concurrent access to data
safe (spinlocks, mutexes, memory barriers, etc.) are designed to prevent
unwanted optimization. If they are being used properly, there will be no
need to use volatile as well. If volatile is still necessary, there is
almost certainly a bug in the code somewhere. In properly-written kernel
code, volatile can only serve to slow things down.
像volatile一樣,核心提供了很多原語來保證並發訪問時的資料安全(自旋鎖, 互斥量,
記憶體屏障等等),同樣可以防止意外的最佳化。如果可以正確使用這些核心原語,那麼就
沒有必要再使用volatile。如果仍然必須使用volatile,那麼幾乎可以肯定在代碼的
某處有一個bug。在正確設計的核心代碼中,volatile能帶來的僅僅是使事情變慢。
Consider a typical block of kernel code:
思考一下這段典型的核心代碼:
spin_lock(&the_lock);
do_something_on(&shared_data);
do_something_else_with(&shared_data);
spin_unlock(&the_lock);
If all the code follows the locking rules, the value of shared_data cannot
change unexpectedly while the_lock is held. Any other code which might
want to play with that data will be waiting on the lock. The spinlock
primitives act as memory barriers - they are explicitly written to do so -
meaning that data accesses will not be optimized across them. So the
compiler might think it knows what will be in shared_data, but the
spin_lock() call, since it acts as a memory barrier, will force it to
forget anything it knows. There will be no optimization problems with
accesses to that data.
如果所有的代碼都遵循加鎖規則,當持有the_lock的時候,不可能意外的改變
shared_data的值。任何可能訪問該資料的其他代碼都會在這個鎖上等待。自旋鎖
原語跟記憶體屏障一樣 —— 它們顯式的用來書寫成這樣 —— 意味著資料訪問
不會跨越它們而被最佳化。所以本來編譯器認為它知道在shared_data裡面將有什麼,
但是因為spin_lock()調用跟記憶體屏障一樣,會強制編譯器忘記它所知道的一切。
那麼在訪問這些資料時不會有最佳化的問題。
If shared_data were declared volatile, the locking would still be
necessary. But the compiler would also be prevented from optimizing access
to shared_data _within_ the critical section, when we know that nobody else
can be working with it. While the lock is held, shared_data is not
volatile. When dealing with shared data, proper locking makes volatile
unnecessary - and potentially harmful.
如果shared_data被聲名為volatile,鎖操作將仍然是必須的。就算我們知道沒有其他人
正在使用它,編譯器也將被阻止最佳化對臨界區內shared_data的訪問。在鎖有效同時,
shared_data不是volatile的。在處理共用資料的時候,適當的鎖操作可以不再需要
volatile - 並且是有潛在危害的。
The volatile storage class was originally meant for memory-mapped I/O
registers. Within the kernel, register accesses, too, should be protected
by locks, but one also does not want the compiler "optimizing" register
accesses within a critical section. But, within the kernel, I/O memory
accesses are always done through accessor functions; accessing I/O memory
directly through pointers is frowned upon and does not work on all
architectures. Those accessors are written to prevent unwanted
optimization, so, once again, volatile is unnecessary.
volatile的儲存類型最初是為那些記憶體映射的I/O寄存器而定義。在核心裡,寄存器訪問
也應該被鎖保護,但是人們也不希望編譯器“最佳化”臨界區內的寄存器訪問。核心裡I/O
的記憶體訪問是通過訪問函數完成的;不贊成通過指標對I/O記憶體的直接存取,並且不是
在所有體系架構上都能工作。那些訪問函數正是為了防止意外最佳化而寫的,因此,再說一
次,volatile類型不是必需的。
Another situation where one might be tempted to use volatile is
when the processor is busy-waiting on the value of a variable. The right
way to perform a busy wait is:
另一種引起使用者可能使用volatile的情況是當處理器正忙著等待一個變數的值。正確執行
一個忙等待的方法是:
while (my_variable != what_i_want)
cpu_relax();
The cpu_relax() call can lower CPU power consumption or yield to a
hyperthreaded twin processor; it also happens to serve as a memory barrier,
so, once again, volatile is unnecessary. Of course, busy-waiting is
generally an anti-social act to begin with.
cpu_relax()調用會降低CPU的能量消耗或者讓位於超執行緒雙處理器;它也作為記憶體
屏障一樣出現,所以,再一次,volatile不是必需的。當然,忙等待一開始就是一種
反常規的做法。
There are still a few rare situations where volatile makes sense in the
kernel:
在核心中,一些稀少的情況下volatile仍然是有意義的:
- The above-mentioned accessor functions might use volatile on
architectures where direct I/O memory access does work. Essentially,
each accessor call becomes a little critical section on its own and
ensures that the access happens as expected by the programmer.
- 在一些體系架構的系統上,允許直接的I/0記憶體訪問,那麼前面提到的訪問函數
可以使用volatile。基本上,每一個訪問函數調用它自己都是一個小的臨界地區並
且保證了按照程式員期望的那樣發生訪問操作。
- Inline assembly code which changes memory, but which has no other
visible side effects, risks being deleted by GCC. Adding the volatile
keyword to asm statements will prevent this removal.
- 某些會改變記憶體的內聯彙編代碼雖然沒有什麼其他明顯的附作用,但是有被GCC刪除
的風險。在彙編聲明中加上volatile關鍵字可以防止這種刪除操作。
- The jiffies variable is special in that it can have a different value
every time it is referenced, but it can be read without any special
locking. So jiffies can be volatile, but the addition of other
variables of this type is strongly frowned upon. Jiffies is considered
to be a "stupid legacy" issue (Linus's words) in this regard; fixing it
would be more trouble than it is worth.
- Jiffies變數是一種特殊情況,雖然每次引用它的時候都可以有不同的值,但讀jiffies
變數時不需要任何特殊的加鎖保護。所以jiffies變數可以使用volatile,但是不贊成
其他跟jiffies相同類型變數使用volatile。Jiffies被認為是一種“愚蠢的遺留物"
(Linus的話)因為解決這個問題比保持現狀要麻煩的多。
- Pointers to data structures in coherent memory which might be modified
by I/O devices can, sometimes, legitimately be volatile. A ring buffer
used by a network adapter, where that adapter changes pointers to
indicate which descriptors have been processed, is an example of this
type of situation.
- 由於某些I/0裝置可能會修改連續一致的記憶體,所以有時,指向連續一致記憶體的資料結構的
指標需要正確的使用volatile。網路介面卡使用的環狀緩衝區正是這類情形的一個例子,
其中適配器用改變指標來表示哪些描述符已經處理過了。
For most code, none of the above justifications for volatile apply. As a
result, the use of volatile is likely to be seen as a bug and will bring
additional scrutiny to the code. Developers who are tempted to use
volatile should take a step back and think about what they are truly trying
to accomplish.
對於大多代碼,除了上述幾種認為正確的情況外都不能使用volatile。所以,使用volatile
是一種bug並且需要對這樣的代碼額外仔細檢查。那些試圖使用volatile的開發人員需要退一
步想想什麼是他們真正試圖實現的。
Patches to remove volatile variables are generally welcome - as long as
they come with a justification which shows that the concurrency issues have
been properly thought through.
非常歡迎刪除volatile變數的補丁 - 只要確信這些補丁正確的完整考慮了並發問題。