你也可以考虑从Linux内核文档中了解相关信息。
C程序员通常认为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中的内容,但是由于自旋锁()调用充当内存屏障,它将强制它忘记它所知道的任何内容。对该数据的访问不会出现任何优化问题。
如果将shared_data声明为易失性,则仍然需要锁定。但是,编译器还将防止在关键部分内对shared_data的访问进行优化,因为我们知道没有其他人可以与其一起工作。在保持锁定时,shared_data不是易失性。在处理共享数据时,适当的锁定使易失性变得不必要-并且可能有害。
易失性存储类最初是为内存映射I/O寄存器而设计的。在内核中,寄存器访问也应受到锁定的保护,但是人们也不希望编译器在关键部分内“优化”寄存器访问。但是,在内核中,I/O内存访问始终通过访问器函数进行;直接通过指针访问I/O内存不受欢迎,并且在所有体系结构上都无法工作。这些访问器被编写为防止不必要的优化,因此,一次又一次地,易失性是不必要的。
当处理器正在忙于等待变量的值时,可能会有人想使用易失性。执行繁忙等待的正确方法是:
while (my_variable != what_i_want)
cpu_relax();
cpu_relax()调用可以降低CPU功耗或让超线程双处理器让出时间片;它也恰好充当内存屏障,因此,一次又一次地,易
volatile
在读取时创建一个内存屏障,因此它可以用作线程安全标志,表示方法已经结束,因为它强制执行了在设置标志之前的代码与之间的happens-before关系。但在C中不是这种情况。 - Monstieur