cpp-内存屏障-MESI

参考博客

CPU内存屏障

在CPU运行时防止指令乱序执行;另外一个功能是保证数据的可见性,即:每一次值的改动,都可以保证被所有相关者看到。这种指令一般都涉及到了==机器指令==,对上层开发者来说,就是汇编指令

  • 在==多核程序上==需要关注

使用

调用具有memory barrier效果的指令可以强制要求CPU产生正确的内存存取顺序,相关的指令有(不限于下面列出的这些指令):

  • GCC 中的某些内联汇编指令
  • Any Win32 Interlocked operation, except on Xbox 360
  • CPP的原子类型相关操作,例如:load(std::memory_order_acquire)
  • POSIX锁操作,例如pthread_mutex_lock
  • 需要注意的是,以上这些操作产生的memory barrier可能是不同类型的

类型

  • LoadLoad
  • StoreStore
  • LoadStore
  • StoreLoad

编译器屏障

对编译器的一种约束,让编译器按要求编译。比如在gcc中有一个定义:
#define barrier() __asm__ __volatile__("": : :"memory")

  • 编写==单核程序时也要==注意

Linux内核提供的内存屏障

  • 对于任何一个类型的内存屏障,用户可以信赖的其最小功能
  • 实现任一种类型的内存屏障,CPU需要提供的最小功能集
  • 如何使用内核提供的内存屏障

CPU应提供的最低保障

  • 有依赖的内存访问会被按序执行
    • Q = READ_ONCE(P); D = READ_ONCE(*Q);对应的CPU实际指令执行顺序应该为:
      • Q = LOAD P, D = LOAD *Q
    • 在某些架构的实现中READ_ONCE也会加入内存屏障,因此实际上CPU执行的指令顺序为:
      • Q = LOAD P, MEMORY_BARRIER, D = LOAD *Q, MEMORY_BARRIER
  • 有重叠操作数的load/store,也会按序执行
    • a = READ_ONCE(*X); WRITE_ONCE(*X, b);对应的CPU执行顺序为:
      • a = LOAD *X, STORE *X = b

内存屏障类型

显式的

  • Write (or Store): 屏障前的指令在屏障后指令之前执行;从内存管理器的角度来看:屏障之前的指令先于屏障之后的指令发往内存控制器
    • ✨写屏障通常与地址依赖屏障/读屏障配对使用
  • 地址依赖屏障(Address-dependency barriers)(weak版的读屏障)
    • 5.9之后的内核移除了显式的该屏障API;但是

      APIs for marking loads from shared variables such as READ_ONCE() and rcu_dereference() provide implicit address-dependency barriers.

  • Read (or Load): 加强版的地址依赖屏障,同样不会影响Write指令
    • ✨读屏障通常与写屏障配对使用
  • 通用屏障:保证在其他部件(内存/设备)看来,屏障前的LOAD/Write指令先于屏障后的Load?Writer指令到达

隐式的

  • ACQUIRE: 保证在其它部件看来,屏障后面的指令在屏障之后到达;但是屏障之前的指令有可能出现在屏障后。
    • 通常与release操作成对出现
    • 相关接口包括:
      • LOCK
      • smp_load_acquire() and smp_cond_load_acquire()
  • RELEASE:保证屏障之前的操作在屏障之前发出;但是后面的操作也可能在屏障之前进行

Address-dependency barriers

There is no guarantee that a CPU will see the correct order of effectsfrom a second CPU’s accesses, even if the second CPU uses a memory
barrier, unless the first CPU also uses a matching memory barrier (see
the subsection on “SMP Barrier Pairing”).

✨这部分内容需要好好看一下!!!

Linux开发时必须/禁止的假设

  • 对于不受 READ_ONCE() WRITE_ONCE() 保护的内存引用,不能假定编译器会按你的意愿行事
  • 不能认为==独立的==加载和存储将按给出的顺序发出(emit)

什么时候需要使用内存屏障

不相关的内存存取操作可能以任意顺序被CPU执行,这对CPU-CPU,CPU-IO来说可能会产生问题;因此在CPU-CPU,IO的场景下,某些关键内存访问顺序需要保证按序执行【举例PHY先写地址,后读写数据】。因此需要给程序员提供一些手段来干预引导==编译器==和==CPU==按序执行指令

它们对屏障两侧的内存操作施加了可感知的部分排序。内存屏障用于覆盖或抑制这些技巧【为了性能进行的指令重排、合并等优化手段】,使代码能够合理地控制多个 CPU 和/或设备的交互。

只有指令操作的内存涉及多个CPU之间,或者与设备之间进行,才需要考虑使用内存屏障

应用开发使用到的粗粒度多核同步技术:自旋锁等,内部实现肯定用到了内存屏障

多核间数据交互

原子操作

访问设备

中断


cpp-内存屏障-MESI
http://example.com/2024/08/18/嵌入式-开发/内存屏障/
作者
Cyokeo
发布于
2024年8月18日
许可协议