您的位置:首页 > 理论基础 > 数据结构算法

为什么不应该使用“volatile”类型

2010-06-09 16:17 411 查看
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变量的补丁 - 只要确信这些补丁正确的完整考虑了并发问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息