kernel 源码中的 ACCESS_ONCE()
2016-07-26 11:22
204 查看
ACCESS_ONCE()也是一个宏,宏定义如下:
#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))
它还有一堆注释,不仔细看的话会很令人困惑,先放在这里,下面再解释:
/*
* Prevent the compiler from merging or refetching accesses. The compiler
* is also forbidden from reordering successive instances of ACCESS_ONCE(),
* but only when the compiler is aware of some particular ordering. One way
* to make the compiler aware of ordering is to put the two invocations of
* ACCESS_ONCE() in different C statements.
*
* This macro does absolutely -nothing- to prevent the CPU from reordering,
* merging, or refetching absolutely anything at any time. Its main intended
* use is to mediate communication between process-level code and irq/NMI
* handlers, all running on the same CPU.
*/
这个宏看起来很奇怪,先取地址,再类型转换,最后又还原成数据。这个宏用来干什么呢?简单点解释就是防止编译器优化代码,它的关键就在于volatile关键字,请看下面的代码段(arch/x86/include/asm/spinlock.h):
如果不使用ACCESS_ONCE()宏,代码就会变成:
由于编译器进行编译时并不会考虑多线程的环境,因此认为在这个循环中lock->tickets.head的值不会发生改变,也就没必要没一次循环都访问一次,因此可能会优化成这个样子:
这样子就可能会出现问题,因为在多线程环境下,lock->tickets.head的值是有可能被其他线程修改的,因此每次循环都访问一次是有必要的。volatile关键字告诉编译器不要做出上面那样的优化,而要每一次循环都访问一次内存。
Linus在一封邮件中认为这样做还能防止另一种形式的优化,将:
优化成:
如果do2部分对a做了修改,就有可能出现问题。但是我认为Linus多虑了,我觉得编译器的设计者和维护者并不会进行如此激进的优化,这种优化很明显在单线程环境下也是可能出现问题的。
至于网上说的volatile关键字是为了防止处理器从缓存中得到错误的数据就大错特错了,任何设计正确的计算机系统都应该能确认cache中是有效数据还是无效数据。
再引用上http://lwn.net/Articles/233479/的一段话:
Your editor’s copy of The C Programming Language, Second Edition (copyright 1988, still known as “the new C book”) has the following to say about the volatile keyword:
The purpose of volatile is to force an implementation to suppress optimization that could otherwise occur. For example, for a machine with memory-mapped input/output, a pointer to a device register might be declared as a pointer to volatile, in order to prevent the compiler from removing apparently redundant references through the pointer.
最后回到开头的注释,上面已经讲得比较多了,注释中的第一段话说到:
Prevent the compiler from merging or refetching accesses.
第二段话又说:
This macro does absolutely -nothing- to prevent the CPU from reordering, merging, or refetching absolutely anything at any time.
所以说,volatile只会对编译器的行为有影响,并不会对CPU产生直接影响,在汇编代码中是不可能看到volatile的。它只在编译期产生作用,在运行期不起作用。
另一篇参考资料:http://lwn.net/Articles/508991/。
#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))
它还有一堆注释,不仔细看的话会很令人困惑,先放在这里,下面再解释:
/*
* Prevent the compiler from merging or refetching accesses. The compiler
* is also forbidden from reordering successive instances of ACCESS_ONCE(),
* but only when the compiler is aware of some particular ordering. One way
* to make the compiler aware of ordering is to put the two invocations of
* ACCESS_ONCE() in different C statements.
*
* This macro does absolutely -nothing- to prevent the CPU from reordering,
* merging, or refetching absolutely anything at any time. Its main intended
* use is to mediate communication between process-level code and irq/NMI
* handlers, all running on the same CPU.
*/
这个宏看起来很奇怪,先取地址,再类型转换,最后又还原成数据。这个宏用来干什么呢?简单点解释就是防止编译器优化代码,它的关键就在于volatile关键字,请看下面的代码段(arch/x86/include/asm/spinlock.h):
do { if (ACCESS_ONCE(lock->tickets.head) == inc.tail) goto out; cpu_relax(); } while (--count);
如果不使用ACCESS_ONCE()宏,代码就会变成:
do { if (lock->tickets.head == inc.tail) goto out; cpu_relax(); } while (--count);
由于编译器进行编译时并不会考虑多线程的环境,因此认为在这个循环中lock->tickets.head的值不会发生改变,也就没必要没一次循环都访问一次,因此可能会优化成这个样子:
head = lock->tickets.head; do { if (head == inc.tail) goto out; cpu_relax(); } while (--count);
这样子就可能会出现问题,因为在多线程环境下,lock->tickets.head的值是有可能被其他线程修改的,因此每次循环都访问一次是有必要的。volatile关键字告诉编译器不要做出上面那样的优化,而要每一次循环都访问一次内存。
Linus在一封邮件中认为这样做还能防止另一种形式的优化,将:
if (a > MEMORY) { do1; do2; do3; } else { do2; }
优化成:
if (a > MEMORY) do1; do2; if (a > MEMORY) do3;
如果do2部分对a做了修改,就有可能出现问题。但是我认为Linus多虑了,我觉得编译器的设计者和维护者并不会进行如此激进的优化,这种优化很明显在单线程环境下也是可能出现问题的。
至于网上说的volatile关键字是为了防止处理器从缓存中得到错误的数据就大错特错了,任何设计正确的计算机系统都应该能确认cache中是有效数据还是无效数据。
再引用上http://lwn.net/Articles/233479/的一段话:
Your editor’s copy of The C Programming Language, Second Edition (copyright 1988, still known as “the new C book”) has the following to say about the volatile keyword:
The purpose of volatile is to force an implementation to suppress optimization that could otherwise occur. For example, for a machine with memory-mapped input/output, a pointer to a device register might be declared as a pointer to volatile, in order to prevent the compiler from removing apparently redundant references through the pointer.
最后回到开头的注释,上面已经讲得比较多了,注释中的第一段话说到:
Prevent the compiler from merging or refetching accesses.
第二段话又说:
This macro does absolutely -nothing- to prevent the CPU from reordering, merging, or refetching absolutely anything at any time.
所以说,volatile只会对编译器的行为有影响,并不会对CPU产生直接影响,在汇编代码中是不可能看到volatile的。它只在编译期产生作用,在运行期不起作用。
另一篇参考资料:http://lwn.net/Articles/508991/。
相关文章推荐
- 从源码安装Mysql/Percona 5.5
- 神器SystemTap
- Linux C函数参考手册(PDF版)
- C# partial关键字说明
- Lua教程(十七):C API简介
- 简单谈谈lua和c的交互
- C#用链式方法表达循环嵌套
- 浅析Ruby的源代码布局及其编程风格
- C#中的委托数据类型简介
- C#编写的艺术字类实例代码
- C#实现打造气泡屏幕保护效果
- 基于C#技术实现身份证识别功能
- 举例讲解C#编程中委托的实例化使用
- 使用C#代码获取存储过程返回值
- C和指针小结(推荐)
- C++中explict关键字用法
- C/C++数据对齐详细解析
- 利用C语言来求最大连续子序列乘积的方法
- 字符串的组合算法问题的C语言实现攻略