[译] 为什么不该在 C/C++ 中使用 volatile gaunthan Posted on May 2 2017 ? Concurrent Programming ? ? Translation ? 原文:[Linux/Documentation/volatile-considered-harmful.txt](http://lxr.free-electrons.com/source/Documentation/volatile-considered-harmful.txt?v=4.0)。 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 修饰的变量,它的值可以在执行线程之外被改变。这导致他们某些时候尝试将 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 的关键在于知道使用它的目的是阻止优化(suppress optimization),而这常常不是大多数使用 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. 如果所有的代码都遵循这段上锁规则,那么在持有锁的时候, 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 寄存器(memory-mapped I/O registers)。在内核中,寄存器的访问同样也应该用锁保护,但同时还不希望编译器优化临界区中的寄存器访问操作(译者注:编译器会认为这些寄存器操作是无用的,然而这些操作实际上会影响硬件设备,只是这些副作用是不可视的)。然而实际上,在内核中 I/O 内存的访问总是通过访问函数(accessor)完成的;直接通过指针访问 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 的情况是处理器忙等(busy-waiting)一个变量的值时。执行忙等操作的正确方式为: 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() 调用会降低处理器功耗或者主动让出时间片给另一个超线程;它恰好也充当内存屏障,因此又一次,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/O 内存访问能够工作的体系结构上使用 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 可以是 volatile,但将这种类型的其他变量修饰为 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/O 设备控制器区域网络修改的一致性内存中的数据结构的指针,某些时候声明为 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 的开发者应该三思而后行。 赏 Wechat Pay Alipay 配置 Vim ,打造你的专属编辑器 有趣的 Linux 命令行程序