今天想要来记录这篇文件 Why the “volatile” type class should not be used
,契机是用 ${linux}/scripts
做 checkpatch.pl 的时候,只要有用到 volatile
这个修饰词,一定会吐出如下的讯息:Use of volatile is usually wrong: see Documentation/volatile-considered-harmful.txt
我们接着看下去~
:Original: :ref:`Documentation/process/volatile-considered-harmful.rst
<volatile_considered_harmful>`
爲什麽不应该使用「volatile」类型
================================
C 语言程序设计师通常认爲 volatile 表示某个变数可以在当前执行绪之外被改变;
因此,在核心程序中用到共享的资料结构时,常常会有 C 语言程序设计师使用 volatile。
换句话说,他们经常会把 volatile 类型当作简易的原子变数,当然它们不是。
在核心程序码中使用 volatile 几乎总是错误的;本文件将解释为何如此。
理解 volatile 的关键是知道它的目的是用来消除优化,实际上很少有人真正需要这样的应用。
在核心中,程序设计师必须防止不预期的并行存取,破坏共享的资料结构,这其实是一个完全不同的应用。
用来防止不预期并行存取的保护措施,可以更加有效率地避免大多数优化相关的问题。
像 volatile 一样,核心提供了很多内建功能来保证并行存取时的资料安全
(自旋锁、互斥旗标、记忆体屏障等等),同样可以防止意外的优化。
如果可以正确使用这些核心功能,那麽就没有必要再使用 volatile。
如果仍然必须使用 volatile,那麽几乎可以肯定在程序码的某处有一个bug。
在正确设计的核心程序码中,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 的情况是,当处理器正 busy-waiting 一个变数。
正确执行一个 busy-waiting 的方法是::
while (my_variable != what_i_want)
cpu_relax();
cpu_relax() 会降低 CPU 的能量消耗或者转交给超执行绪的另一个执行绪;它像是记忆体屏障一样,
所以,再次,volatile 不是必需的。当然,busy-waiting 一开始就是一种反常规的做法。
核心中,在很少数的情况下 volatile 仍然是有意义的:
- 在一些架构上,允许直接的 I/O 记忆体存取,那麽前面提到的存取函数可以使用 volatile。
基本上,每一个存取函数呼叫,它自己都是一个小的临界区域并且保证按程序设计师的期望做存取。
- 某些会改变记忆体状态的内嵌组合语言,虽然没有其他明显的作用,但是有被 GCC 优化掉的可能。
在内嵌组语中加上 volatile 关键字可以防止这种优化。
- Jiffies 变数是一种特殊情况,在於每次读取它的时候都可以有不同的值,
但读取 jiffies 变数时不需要任何特殊的加锁保护。
所以 jiffies 变数可以使用 volatile,但是不赞成其他跟 jiffies 相同类型变数使用 volatile。
Jiffies被认爲是一种"愚蠢的遗留物"(Linus 说的),因爲解决这个问题比保持现状要麻烦的多。
- 由於某些 I/O 设备可能会修改一致性记忆体,
所以有时指向连续一致性记忆体资料结构的指标需要正确的使用 volatile。
网路卡使用的 ring buffer 是这类情形的一个例子,
其中网路卡用改变指标来表示哪些描述子已经处理过了。
对於大多程序码,上述几种可以使用 volatile 的情况都不适用。
所以,使用 volatile 是一种 bug 并且需要对这样的程序码额外仔细检查。
那些试图使用 volatile 的开发人员需要好好思考他们真正想实现的是什麽。
非常欢迎删除 volatile 变量的 patch - 只要证明这些 patch 完整的考虑了并行问题。
参考文件
-------
[1] https://lwn.net/Articles/233481/
[2] https://lwn.net/Articles/233482/
致谢
----
最初由 Randy Dunlap 推动并作初步研究
由 Jonathan Corbet 撰写
参考 Satyam Sharma,Johannes Stezenbach,Jesper Juhl,Heikki Orsila,
H. Peter Anvin,Philipp Hahn 和 Stefan Richter 的意见改善了本文件。
volatile
的实际使用情境如下:(不过 ring buffer 的还没有找到)
在 checkpatch 的时候,还有遇到其他几种不同的 error/warning,也一并记录起来:
今天作为,在一个系列结束和下一个系列开始的,缓冲日,
明天就会正式进入 Cache and TLB Flushing Under Linux
的系列罗!
我们明天见!
>>: [Day14] Android - Kotlin笔记:LiveData在fragment重建时会重新呼叫两次的解决方法
俗话说人要衣装,佛要金装,我们的 vim 也得要有漂亮的外观。今天就让我们来看看如何调教调整 vim...
近期在接 Facebook SDK 做第三方登入时发现 只要不是 Release 版的 apk 就无...
今日文章目录 > - 导览列 > - 练习演示 > - 遇到的问题 > -...
前面我们讲了怎么通过参数的属性限制参数的个数和长度。今天我们来看看,通过参数的属性,限制参数的格式以...
但市面上的裸机Hyperviser还有其他选择(ESXI, Proxmox VE…),为何独锺unR...