Skip to the content.

memory model

使用场景

acquire and release

这是最经典的,如果你什么都记住不是,

前面的 memory operations
          |
          v
       RELEASE
       ACQUIRE
          |
          v
后面的 memory operations

其他

(如果只有这个场景,那么为什么还有这么多的 CPU 定义的 model ?) (或者说,cpp 为什么定义出来这么多的 model )

release consume 暂时不用吧 https://stackoverflow.com/questions/65336409/what-does-memory-order-consume-really-do

回归到原理

cache coherence

  1. 如果 cacheline 的大小那么大,lock 为什么可以只是 lock 一个字节 ? 在同一个 cacheline 上操作,会出现更加严重的 false share

store buffer 和 load buffer 在那里?

相关资料

ARM 的文档

lock would enforce a memory barrier, like an mfence

  1. Loads may be reordered with older stores to different locations but not with older stores to the same location
  2. Locked instructions have a total order

https://www.arangodb.com/2021/02/cpp-memory-model-migrating-from-x86-to-arm/

只有 store-load 存在乱序

https://preshing.com/20120930/weak-vs-strong-memory-models/ https://preshing.com/20120710/memory-barriers-are-like-source-control-operations/

这个

这个讨论有意思的啊 https://github.com/ziglang/zig/issues/6396

有趣的问题 https://bartoszmilewski.com/2008/11/05/who-ordered-memory-fences-on-an-x86/

这种文摘需要看看

https://www.scylladb.com/2018/02/15/memory-barriers-seastar-linux/

[ ] 分析下 ARM 的 memory model,尤其是对比普通 arm 和 Mac arm 的区别

Memory Barriers in the Linux Kernel https://elinux.org/images/a/ab/Bueso.pdf

其他的阅读材料

当分析那么多窒息的例子,都是由于同时访问相同位置的内存,但是访问相同位置的内存的时候,难道不是采用 lock 保护的吗 ? smp_mb 的使用位置和实现方式是什么 ?

// 教程,也许可以阅读一下 :

Acquire and Release Fences

Learn the architecture - Memory Systems, Ordering, and Barriers

可以直接下载 PDF 的

Barrier Litmus Tests and Cookbook

  1. 到底在什么地方使用这个 ?

【计算机体系结构】内存一致性 - 天外飞仙的文章 - 知乎 https://zhuanlan.zhihu.com/p/694673551

https://mp.weixin.qq.com/s/s6AvLiVVkoMX4dIGpqmXYA

https://mp.weixin.qq.com/s/JxyMBHc4qPdozGK32TBTbQ 这个修改真的有这么大的性能提升吗?

https://mp.weixin.qq.com/s/wt5b5e1Y1yG1kDIf0QPsvg 字节团队写的,应该是相当清楚了:

https://mp.weixin.qq.com/s/RCpZK0pD62B6UnMP736L_w 排版很差,讲究看看

似乎视角需要继续抬高: 认识 rcpc rcsc 之前首先认识 release consistency

https://en.wikipedia.org/wiki/Consistency_model

arm 获取时钟的时候,需要使用 isb 来控制 order

static __always_inline u64 __arch_counter_get_cntvct(void)
{
	u64 cnt;

	asm volatile(ALTERNATIVE("isb\n mrs %0, cntvct_el0",
				 "nop\n" __mrs_s("%0", SYS_CNTVCTSS_EL0),
				 ARM64_HAS_ECV)
		     : "=r" (cnt));
	arch_counter_enforce_ordering(cnt);
	return cnt;
}

很好的整理

https://ops101.org/archives/000328.html

https://www.cis.upenn.edu/~devietti/classes/cis601-spring2016/sc_tso.pdf

https://www.zhihu.com/question/583090138/answer/2887780955

https://preshing.com/20120515/memory-reordering-caught-in-the-act/ http://www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.06.07c.pdf

https://mes0903.github.io/memory/memory_model/#programming-language-memory-models

  1. https://people.cs.pitt.edu/~xianeizhang/notes/cpp11_mem.html
  2. wowotech
  3. perfbook : chapter 14.2
    • http://www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.07.23a.pdf
      • 这个 ifso 写过了吗?

既然存在 smp_load_acquire,为什么还需要 smp_mb

核心一句话:smp_load_acquire/smp_store_release 是单向屏障,管不住 Store→Load 的重排; smp_mb 是全屏障,四种重排(LL、LS、SL、SS)全部禁止

只能用 smp_mb 的场景:先写后读(Store→Load)

最经典的就是调度器的睡眠/唤醒配对,include/linux/sched.hset_current_state() 的定义:

/* 等待方 */
for (;;) {
        set_current_state(TASK_UNINTERRUPTIBLE);   /* smp_store_mb(): 先 STORE __state */
        if (CONDITION)                              /* 再 LOAD CONDITION */
                break;
        schedule();
}

/* 唤醒方 */
CONDITION = 1;                    /* 先 STORE */
wake_up_state(p, ...);            /* try_to_wake_up() 里 smp_mb__after_spinlock() 后再 LOAD p->__state */

两侧都是「先写一个变量,再读另一个变量」。如果写操作还停在 store buffer 里就读了对方, 会出现「等待方没睡下、唤醒方也没看到它要睡」→ 丢唤醒。acquire/release 在这里结构上就不够用smp_load_acquire 只保证它之后的访问不提前到它之前,并不阻止它之前的 store 漂到它之后 (反之 release 只管之前的访问不后移)。所以内核专门造了 smp_store_mb()(STORE + full barrier) 放在 set_current_state() 里,并与 kernel/sched/core.ctry_to_wake_up() 的全屏障注释配对:

we need to ensure that CONDITION=1 done by the caller can not be reordered with p->state check below. This pairs with smp_store_mb() in set_current_state()

这类 pattern(Dekker 式双标志、等待队列、membarrier 私有屏障等)在内核里到处都是, 它们的共同点就是需要 SL 顺序。

只能用 smp_mb 的另一个场景:跨 CPU 的全局顺序一致性

Documentation/memory-barriers.txt 的 MULTICOPY ATOMICITY 一节有个专门论证:release-acquire 链不能补偿 非 multicopy-atomic 系统,只有 general barrier 能保证「所有 CPU 对所有操作的顺序达成一致」。 文档里 4 个 CPU 的例子中,cpu0/1/2 用 acquire/release 链、cpu3 用 smp_mb(),结论是链外的 cpu3 完全可能观察到与链上 CPU 矛盾的顺序——release-acquire 的排序是「局部的」,smp_mb() 的排序是全局的。RCU、refcount、percpu-ref 里很多 smp_mb() 就是靠这个性质。

只需要 acquire/release(用 smp_mb 属于浪费)的场景

消息传递(flag + data)模式:

/* 生产者 */                          /* 消费者 */
WRITE_ONCE(buf, data);               if (smp_load_acquire(&flag)) {
smp_store_release(&flag, 1);             /* 保证看到 buf */
                                     }

这里需求只是「读方看到 flag=1 时必须看到 buf 的写」——Load→Load / Store→Store 的单向顺序, acquire/release 恰好够。换成 smp_mb() 也能对,但代价完全不同:ARM64 上 acquire/release 是 ldar/stlr(接近普通访存),smp_mbdmb sy 全系统栅栏;x86 上 acquire 编译成普通 movsmp_mblock 前缀指令(几十到上百 cycle)。锁实现(qspinlock、mutex 的 slowpath)刻意用 acquire/release 而非 mb,就是为了让临界区「吸收」相邻访存、减少流水线冲刷。

小结

需求 acquire/release smp_mb
Load→Load、Store→Store(消息传递、锁) 够,且便宜 能替代但浪费
Store→Load(睡眠/唤醒、双标志互斥) 做不到 唯一选择
全局顺序传播(RCU 等) 链式排序是局部的 有累积性,唯一选择

简记:acquire/release 是性能优化版本的单向屏障,能用就用;凡是「写完必须立刻看到别人的写」 的协议,acquire 结构上给不了保证,只能上 smp_mb

本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。