volatile的进一步深入理解

一、relaxed 是什么意思?

在这里,relaxed 指的是内存访问的顺序不会被强制保证,也就是说:

编译器和 CPU 都可以对这次访问进行重排序优化


举个例子:
// 假设这是 SoC 的寄存器地址
#define DP_CTL    ((volatile uint32_t*)0xF9008000)
#define DP_START  ((volatile uint32_t*)0xF9008004)

*DP_CTL = 0x1;
*DP_START = 0x1;

你想让它“先 enable,后 start”,但如果底层访问是 relaxed 的(比如 writel_relaxed()),那 CPU 可能这么执行:

  • 实际指令顺序:start → enable

这在硬件交互中是灾难性的,因为寄存器顺序不对,设备可能根本不启动。


所以“relaxed”访问:

  • 没有内存屏障(barrier)

  • 不能保证访问顺序

  • 适合“没有依赖关系”的访问,比如配置缓存

  • 不适合用于需要顺序写入的硬件寄存器

在 Linux 内核中:

writel(val, reg);          // 带 barrier(强顺序)
writel_relaxed(val, reg);  // 不带 barrier(弱顺序)


二、dead store elimination 是什么?

它是编译器的一种优化策略:

编译器会删除写入但从未读取的变量,因为它“看起来没用”。


举例:

int a = 42;
a = 43;

编译器会优化掉 a = 42,因为后面立马又改成了 43,前一次写入是“死掉的写入”。


问题来了:

如果这个变量是个 映射的寄存器地址,或会被其他线程读取,那这次“看起来没用”的写入其实是有意义的!

加上 volatile 后,编译器就不会删了


三、为什么volatile ≠ 原子性,但又说它能用于多线程共享变量?


volatile 能用于多线程共享变量吗?

是的,可以用于某些简单的多线程同步场景,比如状态标志、轮询退出等:

volatile bool stop = false;

void worker_thread() {
    while (!stop) {
        // doing work
    }
}

主线程设置 stop = true;,worker 线程能马上看到。

但是,它不能保证原子性

举个例子:
volatile int counter = 0;

thread 1: counter++;
thread 2: counter++;

每个 counter++ 实际是三步:

load → increment → store

多个线程可能交叉执行,造成 丢失更新(lost update)的问题!

所以:

volatile 适合用在 “单向通知” 的变量(比如标志位)
不适合用在 “读改写” 的变量(比如计数器、队列头尾)


正确方式:使用 atomic 或加锁

C++11 起提供了 std::atomic<T>,它:

  • 保证线程间的可见性(类似 volatile

  • 还提供原子性(atomicity)

  • 可选 memory ordering 模型(acquire/release/relaxed)


总结对比表:

特性volatilestd::atomic / 加锁
防编译器优化✅ 是✅ 是
防 CPU 重排序❌ 否✅ 可选(acquire 等)
保证原子性❌ 否✅ 是
多线程下安全⚠️ 有条件✅ 是
驱动开发适用✅ 非常适合❌ 不适合

小结一句话:

  • volatile 保证“每次访问都去读/写内存”,防止编译器优化,但不保证原子性或顺序性

  • relaxed 意味着“CPU 和编译器都可以优化访问顺序”,适合无依赖场景

  • 需要 顺序访问或多线程安全 时,必须加上 memory barrier 或 std::atomic

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值