kernel 源码中的 ACCESS_ONCE()

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/

regmap_access_table函数是Linux内核中regmap API提供的一个函数,用于定义寄存器访问表(register access table),即指定寄存器的访问权限。寄存器访问表是一个由struct regmap_access_table结构体数组组成的表格,每个结构体指定了一个寄存器的地址、大小和访问权限。 在Linux内核中,使用regmap_access_table函数需要按照以下步骤进行: 1. 定义struct regmap_access_table结构体数组:这个数组中的每个元素表示一个寄存器的访问权限。每个元素包含三个字段:reg(寄存器地址)、val_bits(寄存器大小)和writeable(写权限)。例如,以下代码定义了一个包含两个元素的struct regmap_access_table结构体数组: ``` static const struct regmap_access_table my_device_access_table[] = { { 0x00, 4, true }, { 0x04, 4, false }, }; ``` 这个结构体数组表示定义了两个寄存器,第一个寄存器的地址为0x00,大小为4字节,可读可写;第二个寄存器的地址为0x04,大小为4字节,只读不可写。 2. 在regmap_init函数中使用定义好的访问表:在调用regmap_init函数进行寄存器映射初始化时,可以使用定义好的访问表作为参数传递给regmap_init函数的access_table参数,例如: ``` regmap_init_mmio(my_device->base_addr, ..., &my_device_access_table); ``` 这样,在访问寄存器时,regmap API会根据访问表的定义来判断寄存器的访问权限,并做出相应的操作。 总的来说,使用regmap_access_table函数需要定义一个struct regmap_access_table结构体数组,并在regmap_init函数中使用访问表作为参数传递给regmap API。这样,在访问寄存器时,regmap API会根据访问表的定义来判断寄存器的访问权限,并做出相应的操作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值