文章目录

  • DISCLAIMER | 免责声明
  • CONTENTS | 目录
  • 一、ABSTRACT MEMORY ACCESS MODEL | 抽象内存访问模型
    • 1. DEVICE OPERATIONS | 设备操作
    • 2. GUARANTEES | 保障
  • 二、WHAT ARE MEMORY BARRIERS? | 什么是内存屏障?
    • 1. VARIETIES OF MEMORY BARRIER | 内存屏障的种类
      • 1.1 Write (or store) memory barriers | 写内存屏障
      • 1.2 Data dependency barriers | 数据依赖屏障
      • 1.3 Read (or load) memory barriers | 读内存屏障
      • 1.4 General memory barriers | 通用内存屏障
      • 1.5 ACQUIRE operations | 获取操作
      • 1.6 RELEASE operations | 释放操作
      • 1.7 总结
    • 2. WHAT MAY NOT BE ASSUMED ABOUT MEMORY BARRIERS? | 内存障碍可能不包含什么?
    • 3. DATA DEPENDENCY BARRIERS (HISTORICAL) | 数据依赖屏障(历史的)
  • 4. CONTROL DEPENDENCIES | 控制依赖
    • 总结
  • (TODO LINE 923 SMP BARRIER PAIRING)
  • REFERENCES
  • 参考

   在之前的学习中,经常提到 memory-barriers 内存屏蔽的问题,虽然知道大概的意思,但还是比较模糊,所以希望通过官方文档能理解得更加透彻。

DISCLAIMER | 免责声明

          ============================LINUX KERNEL MEMORY BARRIERS============================By: David Howells <dhowells@redhat.com>Paul E. McKenney <paulmck@linux.ibm.com>Will Deacon <will.deacon@arm.com>Peter Zijlstra <peterz@infradead.org>

   本文档不是规范:内容是有意(为了简洁)和无意(由于是人)不完整的。本文档旨在作为使用Linux提供的各种内存屏障的指南,但如果有任何疑问(而且有很多疑问),请询问。一些疑问可以通过参考正式的内存一致性模型和(位于 tools/memory-model/* 的)相关文档来解决。然而,即使是这种内存模型也应该被看作是其维护者的集体意见,而不是一个绝对正确的预言。

   重复一下,本文档并不是 Linux 对硬件期望的规范。

本文档的目的有两个:

  • 指定任何特定屏障可依赖的最小功能,以及
  • 提供如何使用可用屏障的指南。

请注意, 一个体系结构可以为任何特定的屏障提供超过最低要求的内容,但如果体系结构提供的内容少于最低要求,则该体系结构是不正确的。

还需要注意的是,对于架构来说,屏障可能是无操作的(no-op),因为在这种情况下,架构的工作方式使得显式屏障变得不必要。

CONTENTS | 目录

 (*) Abstract memory access model.- Device operations.- Guarantees.(*) What are memory barriers?- Varieties of memory barrier.- What may not be assumed about memory barriers?- Data dependency barriers (historical).- Control dependencies.- SMP barrier pairing.- Examples of memory barrier sequences.- Read memory barriers vs load speculation.- Multicopy atomicity.(*) Explicit kernel barriers.- Compiler barrier.- CPU memory barriers.- MMIO write barrier.(*) Implicit kernel memory barriers.- Lock acquisition functions.- Interrupt disabling functions.- Sleep and wake-up functions.- Miscellaneous functions.(*) Inter-CPU acquiring barrier effects.- Acquires vs memory accesses.- Acquires vs I/O accesses.(*) Where are memory barriers needed?- Interprocessor interaction.- Atomic operations.- Accessing devices.- Interrupts.(*) Kernel I/O barrier effects.(*) Assumed minimum execution ordering model.(*) The effects of the cpu cache.- Cache coherency.- Cache coherency vs DMA.- Cache coherency vs MMIO.(*) The things CPUs get up to.- And then there's the Alpha.- Virtual Machine Guests.(*) Example uses.- Circular buffers.(*) References.

一、ABSTRACT MEMORY ACCESS MODEL | 抽象内存访问模型

  • 考虑系统的以下抽象模型:
                 :                ::                ::                :+-------+   :   +--------+   :   +-------+|       |   :   |        |   :   |       ||       |   :   |        |   :   |       || CPU 1 |<----->| Memory |<----->| CPU 2 ||       |   :   |        |   :   |       ||       |   :   |        |   :   |       |+-------+   :   +--------+   :   +-------+^       :       ^        :       ^|       :       |        :       ||       :       |        :       ||       :       v        :       ||       :   +--------+   :       ||       :   |        |   :       ||       :   |        |   :       |+---------->| Device |<----------+:   |        |   ::   |        |   ::   +--------+   ::                :

   每个CPU执行一个生成内存访问操作的程序。在抽象的CPU中,内存操作的顺序是非常宽松的,只要程序的因果关系保持不变,CPU实际上可以按它喜欢的任何顺序执行内存操作。类似地,编译器也可以按照它喜欢的顺序排列它发出的指令,只要它不影响程序的明显操作。

   因此,在上图中,当其他操作越过CPU与系统其余部分(虚线)之间的接口时,系统的其余部分就会意识到CPU执行的内存操作的效果。

例如,考虑以下事件序列:

 CPU 1       CPU 2=============== ==============={ A == 1; B == 2 }A = 3;     x = B;B = 4;      y = A;

中间的 memory system 所看到的访问集可以按24种不同的组合排列:

STORE A=3, STORE B=4, y=LOAD A->3, x=LOAD B->4
STORE A=3, STORE B=4, x=LOAD B->4, y=LOAD A->3
STORE A=3, y=LOAD A->3, STORE B=4, x=LOAD B->4
STORE A=3, y=LOAD A->3, x=LOAD B->2, STORE B=4
STORE A=3, x=LOAD B->2, STORE B=4, y=LOAD A->3
STORE A=3, x=LOAD B->2, y=LOAD A->3, STORE B=4
STORE B=4, STORE A=3, y=LOAD A->3, x=LOAD B->4
STORE B=4, …

从而产生四种不同的结果组合:

x == 2, y == 1
x == 2, y == 3
x == 4, y == 1
x == 4, y == 3

   此外,CPU提交给 memory system 的 store 指令可能不会被另一个CPU进行的与提交存储顺序相同的负载感知到。

作为另一个示例,请考虑以下事件序列:

 CPU 1           CPU 2=============== ==============={ A == 1, B == 2, C == 3, P == &A, Q == &C }B = 4;          Q = P;P = &B         D = *Q;

这里存在明显的数据依赖性,因为加载到 D 中的值取决于CPU 2从P检索到的地址。在序列的末尾,可能会出现以下任何结果:

(Q == &A) and (D == 1)
(Q == &B) and (D == 2)
(Q == &B) and (D == 4)

请注意, CPU 2决不会尝试将C加载到D中,因为在发布 *Q 之前,CPU会将P加载到Q中。

1. DEVICE OPERATIONS | 设备操作

   某些设备将其控制接口显示为存储器位置的集合,但是访问控制寄存器的顺序非常重要。例如,假设一个以太网卡具有一组内部寄存器,可以通过地址端口寄存器(A)和数据端口寄存器(D)访问这些寄存器。为了读取内部寄存器5,然后可以使用以下代码:

*A =  5;x = *D;

但这可能显示为以下两个序列之一:

  • STORE *A = 5, x = LOAD *D
  • x = LOAD *D, STORE *A = 5

第二个几乎肯定会导致故障,因为它在尝试读取寄存器设置了地址。

2. GUARANTEES | 保障

期望 CPU 能保障如下最低要求:

  • 在任何给定的CPU上,相关内存访问将相对于其自身顺序进行。 这意味着:

    Q = READ_ONCE(P); D = READ_ONCE(*Q);

    CPU将发出以下内存操作:

    Q = LOAD P, D = LOAD *Q

    并始终按此顺序。 但是,在DEC Alpha上,READ_ONCE()也发出一条内存屏障指令,因此DEC Alpha CPU将发出以下内存操作:

    Q = LOAD P, MEMORY_BARRIER, D = LOAD *Q, MEMORY_BARRIER

    无论是否在DEC Alpha上,READ_ONCE()都可以防止编译器“恶作剧”。

  • 特定CPU内的重叠 LOAD 和 STORE 将在该CPU内排序。 这意味着:

    a = READ_ONCE(*X); WRITE_ONCE(*X, b);

    CPU将仅发出以下顺序的内存操作:

    a = LOAD *X, STORE *X = b

    并且对于:

    WRITE_ONCE(*X, c); d = READ_ONCE(*X);

    CPU只会发出:

    STORE *X = c, d = LOAD *X

    (如果 LOAD 和 STORE 指令作用在相同的内存上,则它们会按顺序执行)。

并假设有许多"必须"或"不可"的因素:

  • 不能假定编译器在编译没有受 READ_ONCE() 和 WRITE_ONCE() 保护的内存引用时,还能正确执行你想要的内存访问顺序。没有它们,编译器将有权进行各种“创造性”的转换,这在“编译器屏障 (COMPILER BARRIER)”部分中有介绍。

  • 不能假定独立的 loads 和 stores 指令会按照你给定的顺序发布。这意味着:

    X = *A; Y = *B; *D = Z;

    我们可能会获得以下任何序列:

    X = LOAD *A, Y = LOAD *B, STORE *D = Z
    X = LOAD *A, STORE *D = Z, Y = LOAD *B
    Y = LOAD *B, X = LOAD *A, STORE *D = Z
    Y = LOAD *B, STORE *D = Z, X = LOAD *A
    STORE *D = Z, X = LOAD *A, Y = LOAD *B
    STORE *D = Z, Y = LOAD *B, X = LOAD *A

  • 必须假定访问同一个内存的操作会合并(merged or discarded),这意味着:

    X = *A; Y = *(A + 4);

    我们可能会获得以下任一序列:

    X = LOAD *A; Y = LOAD *(A + 4);
    Y = LOAD *(A + 4); X = LOAD *A;
    {X, Y} = LOAD {*A, *(A + 4) }; // merged

    并且对于:

    *A = X; *(A + 4) = Y;

    我们可能会得到以下任何一种:

    STORE *A = X; STORE *(A + 4) = Y;
    STORE *(A + 4) = Y; STORE *A = X;
    STORE {*A, *(A + 4) } = {X, Y}; // merged

并且有反担保(anti-guarantees):

  • 上面的保证不适用于位域(bitfields),因为编译器通常会生成代码以使用非原子的读取-修改-写入(non-atomic read-modify-write)序列对其进行修改。 不要尝试使用位域(bitfields)来同步并行算法。

  • 即使在位域受锁保护的情况下,给定位域中的所有字段也必须受一个锁保护。如果给定位域中的两个域由不同的锁保护,则编译器的非原子性读-修改-写序列可能导致对一个域的更新破坏相邻域的值。

  • 这些保证仅适用于正确对齐且大小合适的标量变量。“适当大小”当前是指与“ char”,“ short”,“ int”和“ long”相同大小的变量。“正确对齐”是指自然的对齐,因此“char”不需要对齐,“short”需要两字节对齐,“int”需要四字节对齐,“long”在32位或64位系统上需要四字节或八字节对齐。请注意,这些保证是C11标准中引入的,因此在使用较早的C11之前的编译器(例如gcc 4.6)时要当心。标准中包含此保证的部分为3.14节,该节将“内存位置(memory location)”定义如下:

    memory location
    标量类型的对象或相邻位域的最大序列都具有非零宽度

    注意1: 两个执行线程可以更新和访问单独的内存位置,而不会互相干扰。

    NOTE 2: A bit-field and an adjacent non-bit-field member are in separate memory locations. The same applies to two bit-fields, if one is declared inside a nested structure declaration and the other is not, or if the two are separated by a zero-length bit-field declaration, or if they are separated by a non-bit-field member declaration.

二、WHAT ARE MEMORY BARRIERS? | 什么是内存屏障?

   从上面可以看出,独立的内存操作有效地以随机顺序执行,但这对于 CPU-CPU 和 I/O 的交互可能是个问题。 所需要的是一种干预方式,以指示编译器和CPU限制顺序。

   内存屏障就是这种干预。对屏障两侧的内存操作限制了顺序。 这样的执行很重要,因为系统中的CPU和其他设备可以使用各种技巧来提高性能,包括重新排序,推迟和组合内存操作。 投机负荷; 投机分支预测和各种类型的缓存。内存屏障用于覆盖或抑制这些技巧,从而使代码可以合理地控制多个CPU和/或devices的交互。

1. VARIETIES OF MEMORY BARRIER | 内存屏障的种类

内存屏障有四个基本种类:

1.1 Write (or store) memory barriers | 写内存屏障

   Write memory barriers 可以保证该屏障之前的 STORE 操作一定能在屏障之后的 STORE 操作之前。

   Write barriers 对 STORE 操作的顺序有影响,对负载(loads)没有任何影响。

   可以将CPU视为随着时间的推移向存储系统提交一系列 STORE 操作。 写屏障之前的所有 STORE 都将发生在写屏障之后的所有 STORE 之前。

   [!] 请注意,写屏障通常应与读或数据依赖屏障配对使用; 请参阅 “SMP barrier pairing | SMP屏障配对”小节。

1.2 Data dependency barriers | 数据依赖屏障

   数据依赖障碍是读取障碍的较弱形式。 如果执行了两次加载,使得第二次加载取决于第一次加载的结果(例如:第一次加载获取第二次加载将定向到的地址),则将需要数据相关性屏障来确保 在访问由第一次加载获得的地址之后,更新第二次加载的目标。

   A data dependency barrier is a partial ordering on interdependent loads only; it is not required to have any effect on stores, independent loads or overlapping loads.

   数据依赖屏障只是对相互依赖的负载(loads)产生顺序的影响。对 stores、没有依赖关系的 loads 或是重叠的 loads 没有影响。

   如(1.1 write barriers)所述,the other CPUs in the system can be viewed as committing sequences of stores to the memory system that the CPU being considered can then perceive. A data dependency barrier issued by the CPU under consideration guarantees that for any load preceding it, if that load touches one of a sequence of stores from another CPU, then by the time the barrier completes, the effects of all the stores prior to that touched by the load will be perceptible to any loads issued after the data dependency barrier.

   请参阅“Examples of memory barrier sequences | 内存屏障序列示例”小节的图,图中展示了对顺序的约束。

   [!] 请注意,first load 实际上必须具有数据依赖性而不是控制依赖性。如果 second load 的地址依赖于 first load,但是该依赖关系取决于条件而不是实际加载地址本身,则它是控制依赖关系,因此需要完全的 read barrier 或更高的要求。请见 “Control dependencies | 控制依赖”小节获得更多信息。

   [!] 请注意,数据依赖屏障通常应与写入屏障配对使用; 请参阅“SMP barrier pairing | SMP屏障配对”小节。

1.3 Read (or load) memory barriers | 读内存屏障

   读屏障是数据依赖屏障,并保证相对于系统的其他组件,屏障之前指定的所有LOAD操作似乎都发生在屏障之后指定的所有LOAD操作之前。

   读屏障是仅对 LOAD 的部分排序; 不对 STORE 有任何影响。

   读内存障碍意味着数据依赖障碍,因此可以替代它们。

   [!] 请注意,写屏障通常应与读或数据依赖屏障配对使用; 请参阅 “SMP barrier pairing | SMP屏障配对”小节。

1.4 General memory barriers | 通用内存屏障

   通用内存屏障可确保相对于系统的其他组件,屏障之前指定的所有LOAD和STORE操作似乎都发生在屏障之后指定的所有LOAD和STORE操作之前。

   通用内存屏障是对 LOAD 和 STORE 的部分排序。

   通用内存屏障意味着读和写内存屏障,因此可以替代任何一种。

还有一些隐式变体:

1.5 ACQUIRE operations | 获取操作

   这充当单向渗透屏障。 它保证了ACQUIRE操作之后的所有内存操作都将发生在ACQUIRE操作之后,相对于系统的其他组件。 ACQUIRE操作包括LOCK操作以及 smp_load_acquire() 和 smp_cond_load_acquire() 操作。

   在ACQUIRE操作之前发生的内存操作可能似乎在完成之后发生。

   几乎应该将ACQUIRE操作与RELEASE操作配对使用。

1.6 RELEASE operations | 释放操作

   这也充当单向渗透屏障。 它保证相对于系统的其他组件,RELEASE操作之前的所有内存操作似乎都发生在RELEASE操作之前。 RELEASE操作包括 UNLOCK 操作和 smp_store_release() 操作。

   在RELEASE操作之后发生的内存操作可能似乎在其完成之前发生。

   通常使用ACQUIRE和RELEASE操作排除了对其他类型的内存屏障的需要(但请注意“ MMIO写屏障”小节中提到的例外)。 此外,RELEASE + ACQUIRE对不能保证充当完整的内存屏障。 但是,在对给定变量执行ACQUIRE之后,可以保证对该变量进行任何先前的RELEASE之前的所有内存访问都是可见的。 换句话说,在给定变量的关键部分内,可以保证对该变量的所有先前关键部分的所有访问均已完成。

   这意味着ACQUIRE充当最小的“获取”操作,RELEASE充当最小的“释放”操作。

1.7 总结

   除了完全有序和宽松(无障碍语义)定义之外,atomic_t.txt中描述的原子操作的子集还具有ACQUIRE和RELEASE变体。 对于同时执行加载和存储的复合原子,ACQUIRE语义仅适用于加载,而RELEASE语义仅适用于操作的存储部分。

   仅在两个CPU之间或CPU与设备之间可能存在交互的情况下才需要内存屏障。 如果可以保证在任何特定的代码段中都不会发生任何此类交互,那么在该段代码中就不需要内存屏障。

   请注意,这些是最低保证。 不同的体系结构可能会提供更多的实质性保证,但是可能不会依赖于特定于体系结构的代码。

2. WHAT MAY NOT BE ASSUMED ABOUT MEMORY BARRIERS? | 内存障碍可能不包含什么?

Linux内核内存障碍不能保证某些事情:

  • 不能保证在完成内存屏障指令之前完成在内存屏障之前指定的任何内存访问; 可以认为该屏障在CPU的访问队列中划了一条线,适当类型的访问可能不会交叉。
  • 无法保证在一个CPU上发出内存屏障将对另一CPU或系统中的任何其他硬件产生直接影响。 间接效果将是第二个CPU看到第一个CPU访问的效果发生的顺序,但请看下一点:
  • 即使第二个CPU使用了内存屏障,也不能保证一个CPU将从第二个CPU的访问中看到正确的效果顺序,除非第一个CPU也使用了匹配的内存屏障(请参阅“ SMP屏障配对”小节) )。
  • 无法保证某些介入的 off-the-CPU hardware[*]不会重新排序内存访问。 CPU缓存一致性机制应在CPU之间传播内存屏障的间接影响,但可能不会这样做。

[*] For information on bus mastering DMA and coherency please read:

Documentation/driver-api/pci/pci.rst
Documentation/DMA-API-HOWTO.txt
Documentation/DMA-API.txt

3. DATA DEPENDENCY BARRIERS (HISTORICAL) | 数据依赖屏障(历史的)

   从Linux内核v4.15开始,在READ_ONCE()中添加了smp_read_barrier_depends(),这意味着仅需要关注本部分的人员是从事DEC Alpha体系结构特定代码的人员以及正在从事该工作的人员。 READ_ONCE()本身。 对于需要它的人以及对历史感兴趣的人,这里有数据依赖障碍的故事。

   数据依赖障碍的使用要求有些微妙,并且不一定总是需要它们。 为了说明,请考虑以下事件序列:

         CPU 1                 CPU 2===============       ==============={ A == 1, B == 2, C == 3, P == &A, Q == &C }B = 4;<write barrier>WRITE_ONCE(P, &B)Q = READ_ONCE(P);D = *Q;

   这里有一个明确的数据相关性,并且看起来在序列结束时,Q必须是&A或&B,并且:

(Q == &A) implies (D == 1)
(Q == &B) implies (D == 4)

   但! CPU 2 对 P 的感知可能在对 B 的更新之前,从而导致以下情况:

(Q == &B) and (D == 2) ???

   尽管这看起来像是一致性或因果关系维护的失败,但事实并非如此,可以在某些实际CPU(例如DEC Alpha)上观察到此行为。

   为了解决这个问题,必须在 address load 和 data load 之间插入数据依赖屏障或更好的屏障:

CPU 1                    CPU 2
===============          ===============
{ A == 1, B == 2, C == 3, P == &A, Q == &C }
B = 4;
<write barrier>
WRITE_ONCE(P, &B);Q = READ_ONCE(P);<data dependency barrier>D = *Q;

这强制了两种含义之一的发生,并防止了第三种可能性的出现。

[!] 请注意,这种极不符合直觉的情况在具有拆分式缓存的计算机上最容易出现,因此,例如,一个缓存组处理偶数个缓存行,而另一组处理奇数个缓存行。 指针P可以存储在奇数高速缓存行中,变量B可以存储在偶数高速缓存行中。 然后,如果读取CPU的高速缓存的偶数存储区非常忙,而奇数存储区则处于空闲状态,则可以看到指针P(&B)的新值,但是变量B的旧值(2) 。

   排序依赖的写入不需要数据依赖障碍,因为Linux内核支持的CPU在确定(1)该写入将实际发生,(2)该写入的位置以及确定它们的位置之前不会进行写入。 (3)要写入的值。 但是,请仔细阅读“控制权”部分和Documentation / RCU / rcu_dereference.txt文件:编译器可以并且确实以许多极富创造力的方式打破了依赖关系。

CPU 1                    CPU 2
===============          ===============
{ A == 1, B == 2, C = 3, P == &A, Q == &C }
B = 4;
<write barrier>
WRITE_ONCE(P, &B);Q = READ_ONCE(P);WRITE_ONCE(*Q, 5);

因此,不需要数据相关性障碍就可以将读入Q并将存储存储到* Q中。 换句话说,即使没有数据依赖障碍,也禁止这种结果:

   请注意,这种模式应该很少见。 毕竟,依存关系排序的全部要点是-防止对数据结构的写入,以及与这些写入相关联的昂贵的高速缓存未命中。 此模式可用于记录罕见的错误情况等,并且CPU的自然排序可防止此类记录丢失。

   请注意,数据依赖项提供的排序对于包含它的CPU是本地的。 有关更多信息,请参见“Multicopy atomicity | 多原子性”部分。

   例如,数据依赖障碍对于RCU系统非常重要。 请参阅include / linux / rcupdate.h中的rcu_assign_pointer()和rcu_dereference()。 这允许用新的修改后的目标替换RCU指针的当前目标,而替换目标似乎并未完全初始化。

   另请参阅“Cache Coherency | 高速缓存一致性”小节,以获取更详尽的示例。

4. CONTROL DEPENDENCIES | 控制依赖

   控制依赖项可能会有些棘手,因为当前的编译器无法理解它们。 本节的目的是帮助您防止编译器的无知破坏代码。

   load-load 控制依赖关系需要完整的读取内存屏障,而不仅仅是数据依赖关系屏障才能使其正常工作。 考虑以下代码:

q = READ_ONCE(a);
if (q) {<data dependency barrier>  /* BUG: No data dependency!!! */p = READ_ONCE(b);
}

   这将不会产生预期的效果,因为没有实际的数据相关性,而是一种控制相关性,即CPU可能会通过尝试预先预测结果来短路,从而使其他CPU看到b中的负载是在发生负载之前发生的。 在这种情况下,实际需要的是:

q = READ_ONCE(a);
if (q) {<read barrier>p = READ_ONCE(b);
}

但是,STORE 并不是会被猜测的。 这意味着为 load-store 控制依赖项提供了排序,如以下示例所示:

q = READ_ONCE(a);
if (q) {WRITE_ONCE(b, 1);
}

   控制依赖项通常与其他类型的障碍配对。 也就是说,请注意,READ_ONCE()和WRITE_ONCE()都不是可选的! 如果没有READ_ONCE(),则编译器可能会将“ a”的负载与“ a”的其他负载合并。 如果没有WRITE_ONCE(),编译器可能会将存储合并为“ b”,而将其他存储合并为“ b”。 两者都会对订购产生高度违反直觉的影响。

   更糟糕的是,如果编译器能够证明(说)变量’a’的值始终非零,那么通过消除如下所示的“ if”语句来优化原始示例将是其应有的权利:

q = a;
b = 1;  /* BUG: Compiler and CPU can both reorder!!! */

因此,请不要忽略READ_ONCE()。

   试图在“ if”语句的两个分支上的相同存储上强制执行排序很诱人,如下所示:

q = READ_ONCE(a);
if (q) {barrier();WRITE_ONCE(b, 1);do_something();
} else {barrier();WRITE_ONCE(b, 1);do_something_else();
}

不幸的是,当前的编译器将以高优化级别将其转换如下:

q = READ_ONCE(a);
barrier();
WRITE_ONCE(b, 1);  /* BUG: No ordering vs. load from a!!! */
if (q) {/* WRITE_ONCE(b, 1); -- moved up, BUG!!! */do_something();
} else {/* WRITE_ONCE(b, 1); -- moved up, BUG!!! */do_something_else();
}

   现在,从’a’和存储到’b’的加载之间没有条件,这意味着CPU有权对其进行重新排序:该条件是绝对必需的,即使在毕竟汇编代码中也必须存在该条件 编译器优化已得到应用。 因此,如果在此示例中需要排序,则需要显式的内存屏障,例如smp_store_release():

q = READ_ONCE(a);
if (q) {smp_store_release(&b, 1);do_something();
} else {smp_store_release(&b, 1);do_something_else();
}

相反,如果没有显式的内存屏障,则只有在 STORE 不同时才能保证两足的控制顺序,例如:

q = READ_ONCE(a);
if (q) {WRITE_ONCE(b, 1);do_something();
} else {WRITE_ONCE(b, 2);do_something_else();
}

仍需要初始READ_ONCE()来防止编译器证明’a’的值。

   另外,您需要注意对局部变量“ q”的处理,否则编译器可能会猜测该值并再次删除所需的条件。 例如:

q =READ_ONCE(a);
if (q % MAX) {WRITE_ONCE(b, 1);do_something();
} else {WRITE_ONCE(b, 2);do_something_else();
}

如果将MAX定义为1,则编译器知道(q%MAX)等于零,在这种情况下,编译器有权利将上述代码转换为以下代码:

q = READ_ONCE(a);
WRITE_ONCE(b, 2);
do_something_else();

   通过这种转换,不需要CPU遵守从变量“ a”到存储区到变量“ b”之间的加载顺序。 尝试添加barrier()很诱人,但这无济于事。 有条件的已经消失,障碍也不会把它带回来。 因此,如果您依赖此顺序,则应确保MAX大于1,也许如下所示:

q = READ_ONCE(a);
BUILD_BUG_ON(MAX <= 1); /* Order load from a with store to b. */
if (q % MAX) {WRITE_ONCE(b, 1);do_something();
} else {WRITE_ONCE(b, 2);do_something_else();
}

请再次注意,“ b”存储区有所不同。 如前所述,如果它们相同,则编译器可以将此存储拉到’if’语句之外。

   您还必须注意不要过多地依赖布尔短路评估。 考虑以下示例:

q = READ_ONCE(a);
if (q || 1 > 0)WRITE_ONCE(b, 1);

因为第一个条件不能出错,而第二个条件始终为真,所以编译器可以按如下所示变换此示例,从而消除了控件依赖性:

q =READ_ONCE(a);
WRITE_ONCE(b, 1);

   此示例强调了确保编译器不会猜测您的代码的需要。 更一般而言,尽管READ_ONCE()确实会强制编译器针对给定的负载实际发出代码,但不会强制编译器使用结果。

   此外,控制依赖项仅适用于所讨论的if语句的从句和else子句。 特别是,它不一定适用于if语句之后的代码:

q = READ_ONCE(a);
if (q) {WRITE_ONCE(b, 1);
} else {WRITE_ONCE(b, 2);
}
WRITE_ONCE(c, 1);  /* BUG: No ordering against the read from 'a'. */

   很有说服力的是,实际上是有序的,因为编译器无法对易失性访问进行重新排序,也无法根据条件对“ b”的写操作进行重新排序。 不幸的是,出于这种推理,编译器可能会将这两个对b的写操作作为条件移动指令进行编译,例如在这种奇妙的伪汇编语言中:

ldr1,a
cmp r1,$0
cmov,ne r4,$1
cmov,eq r4,$2
st r4,b
st $1,c

   顺序较弱的CPU在从“ a”到存储到“ c”的负载之间将没有任何依赖关系。 控制依赖项将仅扩展到这对cmov指令和取决于它们的存储。 简而言之,控制依赖项仅适用于所讨论的if语句的then子句和else子句中的存储(包括由这两个子句调用的函数),而不适用于该if语句之后的代码。

请注意,控制依赖项提供的排序是包含它的CPU本地的。 有关更多信息,请参见“Multicopy atomicity | 多份原子性”部分。

总结

  • Control dependencies can order prior loads against later stores. However, they do -not- guarantee any other sort of ordering: Not prior loads against later loads, nor prior stores against later anything. If you need these other forms of ordering, use smp_rmb(), smp_wmb(), or, in the case of prior stores and later loads, smp_mb().
  • If both legs of the “if” statement begin with identical stores to the same variable, then those stores must be ordered, either by preceding both of them with smp_mb() or by using smp_store_release() to carry out the stores. Please note that it is -not- sufficient to use barrier() at beginning of each leg of the “if” statement because, as shown by the example above, optimizing compilers can destroy the control dependency while respecting the letter of the barrier() law.
  • Control dependencies require at least one run-time conditional between the prior load and the subsequent store, and this conditional must involve the prior load. If the compiler is able to optimize the conditional away, it will have also optimized away the ordering. Careful use of READ_ONCE() and WRITE_ONCE() can help to preserve the needed conditional.
  • Control dependencies require that the compiler avoid reordering the dependency into nonexistence. Careful use of READ_ONCE() or atomic{,64}_read() can help to preserve your control dependency. Please see the COMPILER BARRIER section for more information.
  • Control dependencies apply only to the then-clause and else-clause of the if-statement containing the control dependency, including any functions that these two clauses call. Control dependencies do -not- apply to code following the if-statement containing the control dependency.
  • Control dependencies pair normally with other types of barriers.
  • Control dependencies do -not- provide multicopy atomicity. If you need all the CPUs to see a given store at the same time, use smp_mb().
  • Compilers do not understand control dependencies. It is therefore your job to ensure that they do not break your code.

(TODO LINE 923 SMP BARRIER PAIRING)

   

   

   

   

   

   

   

   

   

   

   

   

   

   

   

   
   

   

   

REFERENCES

Alpha AXP Architecture Reference Manual, Second Edition (Sites & Witek,
Digital Press)Chapter 5.2: Physical Address Space CharacteristicsChapter 5.4: Caches and Write BuffersChapter 5.5: Data SharingChapter 5.6: Read/Write OrderingAMD64 Architecture Programmer's Manual Volume 2: System ProgrammingChapter 7.1: Memory-Access OrderingChapter 7.4: Buffering and Combining Memory WritesARM Architecture Reference Manual (ARMv8, for ARMv8-A architecture profile)Chapter B2: The AArch64 Application Level Memory ModelIA-32 Intel Architecture Software Developer's Manual, Volume 3:
System Programming GuideChapter 7.1: Locked Atomic OperationsChapter 7.2: Memory OrderingChapter 7.4: Serializing InstructionsThe SPARC Architecture Manual, Version 9Chapter 8: Memory ModelsAppendix D: Formal Specification of the Memory ModelsAppendix J: Programming with the Memory ModelsStorage in the PowerPC (Stone and Fitzgerald)UltraSPARC Programmer Reference ManualChapter 5: Memory Accesses and CacheabilityChapter 15: Sparc-V9 Memory ModelsUltraSPARC III Cu User's ManualChapter 9: Memory ModelsUltraSPARC IIIi Processor User's ManualChapter 8: Memory ModelsUltraSPARC Architecture 2005Chapter 9: MemoryAppendix D: Formal Specifications of the Memory ModelsUltraSPARC T1 Supplement to the UltraSPARC Architecture 2005Chapter 8: Memory ModelsAppendix F: Caches and Cache CoherencySolaris Internals, Core Kernel Architecture, p63-68:Chapter 3.3: Hardware Considerations for Locks andSynchronizationUnix Systems for Modern Architectures, Symmetric Multiprocessing and Caching
for Kernel Programmers:Chapter 13: Other Memory ModelsIntel Itanium Architecture Software Developer's Manual: Volume 1:Section 2.6: SpeculationSection 4.4: Memory Access

参考

  • linux-5.4.37/Documentation/memory-barriers.txt
  • Linux内核中的内存屏障

[Linux Kernel] memory-barriers 内存屏蔽 官方文档相关推荐

  1. 《LINUX KERNEL MEMORY BARRIERS》

    <LINUX KERNEL MEMORY BARRIERS> 原文地址:https://www.kernel.org/doc/Documentation/memory-barriers.t ...

  2. 从LFS官方文档构建完整Linux系统

    这不是新手教程!!! Parallels Desktop (为防止找不到网卡 NIC Type设成Intel(R) PRO/1000 MT). kali-linux-2.0-amd64(i386).i ...

  3. linux3.10.53编译,根据官方文档在Linux下编译安装Apache

    根据官方文档在Linux下编译安装Apache 前言 永远记住官方文档才是最准确的安装手册,这篇文章仅为对官方文档的解读和补充,学习提升务必阅读官方文档: http://httpd.apache.or ...

  4. 【官方文档】Fluent Bit 安装在 Linux

    文章目录 1. Amazon Linux 2. Redhat / CentOS 2.1. 安装在 Redhat / CentOS 上 2.2. 配置 Yum 2.3. 安装 3. Debian 4. ...

  5. 【官方文档】Fluentd 通过 RPM 包安装在 Red Hat Linux

    文章目录 1. td-agent 是什么? 2. calyptia-fluentd 是什么? 3. 用于安装 td-agent 3.1. 步骤 0:安装前 3.2. 步骤 1:从 rpm Reposi ...

  6. HBase 官方文档

    HBase 官方文档 Copyright © 2010 Apache Software Foundation, 盛大游戏-数据仓库团队-颜开(译) Revision History Revision ...

  7. Sklearn官方文档中文整理4——随机梯度下降和最近邻篇

    Sklearn官方文档中文整理4--随机梯度下降和最近邻篇 1. 监督学习 1.5. 随机梯度下降 1.5.1. 分类[linear_model.SGDClassifier] 1.5.2. 回归[li ...

  8. 【官方文档】Fluent Bit 数据管道之过滤插件(Kubernetes)

    文章目录 1. 配置参数 2. 处理 'log' 值 3. Kubernetes Annotations 3.1. Pod 定义中的 annotations 示例 3.1.1. 建议一个解析器 3.1 ...

  9. Centos 7.6 服务器安装oracle 11gR2(参考官方文档)

    Centos 7.6 服务器安装oracle 11gR2 说来气人,项目需要所以得在服务器上安装Oracle. 像往常一样,打开浏览器 -> 搜索 - > 找博客 -> 安装. 但是 ...

最新文章

  1. UA MATH567 高维统计III 随机矩阵12 整数环上的区间的应用:DNA序列突变点侦测的统计量及假设检验
  2. 如何成为国内敏捷BI领跑者?这家企业的经验值得借鉴
  3. Java 序列化之 Externalizable
  4. 高内聚低耦合_高渗透环氧树脂灌浆料
  5. 晶振测试与使用中的主要问题(z)
  6. VLC简介及使用说明
  7. 从零开始学Pytorch(十五)之数据增强
  8. 移动增值短信平台实施计划方案(珠海报业短信)
  9. Wowza流媒体Live直播和VOD点播配置实战
  10. 计算机应用基础周记,2800字计算机应用基础实习报告范文.doc
  11. 简单无迹kalman的matlab程序,卡尔曼滤波原理及应用——MATLAB仿真
  12. 我的网名--荡涤心灵
  13. Android机顶盒M1上ANR问题的排查方法和应对方案
  14. 【DP + 思维】A Question of Ingestion Gym - 101673G
  15. [牛客网]万万没想到之抓捕孔连顺
  16. 原生js实现横向 tab 栏切换,选中项自动滚动居中
  17. 快鲸智慧社区平台:快速提升“智慧社区”服务水平
  18. 计算机信息管理学籍,计算机信息管理毕业论文学籍管理系统.doc
  19. python金字塔函数_Pyramid Python量化学习笔记:API的基本方法,金字塔
  20. 爬虫练习(1)-- 爬取豆瓣最新电影

热门文章

  1. vue中下载文件导出保存到本地
  2. C 语言时间函数 秒 毫秒 微秒
  3. 从零开始perp交叉编译及配置
  4. Dockers-搭建本地私有仓库
  5. Docker | 基于docker安装Redis
  6. android上调试H5小工具
  7. uniapp实现附有二维码的图片的本地保存
  8. 中国科大: 那些杀手都很冷[ZT]
  9. linux 笔记本合盖不休眠设置
  10. html5 指南针,html5指南针实现