kernel 源码中的 ACCESS_ONCE()

来源:互联网 发布:mac里面怎么卸载程序 编辑:程序博客网 时间:2024/05/09 16:44

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):

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/。

0 0