news 2026/9/16 18:47:25

Linux 内核内存序操作完全指南:基于 LKMM ordering 文档的屏障、有序访问与无序访问分类解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux 内核内存序操作完全指南:基于 LKMM ordering 文档的屏障、有序访问与无序访问分类解析

Linux 内核内存序操作完全指南:基于 LKMM ordering 文档的屏障、有序访问与无序访问分类解析

【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux

导读

本文围绕 Linux 内核内存模型(LKMM)的官方排序文档展开,系统梳理内核为并发编程提供的全部内存序(memory-ordering)操作,将其归纳为"屏障(Barriers)""有序内存访问(Ordered Memory Accesses)""无序访问(Unordered Accesses)"三大类,并逐一给出语义定义、适用场景与代码示例。读完本文,你将能够:准确区分smp_mb()smp_wmb()smp_rmb()barrier()等屏障的强弱差异;正确选用smp_store_release()/smp_load_acquire()rcu_assign_pointer()/rcu_dereference()等有序访问原语;并深刻理解控制依赖与未标记 C 语言访问的陷阱,从而写出可移植、可验证的无锁并发代码。

本文的骨架内容源自 Documentation/dev-tools/lkmm/docs/ordering.rst,该文档通过kernel-include指令以字面形式(literal include)引入 tools/memory-model/Documentation/ordering.txt;文中补充的源码证据来自 include/asm-generic/barrier.h、tools/memory-model/Documentation/cheatsheet.txt、tools/memory-model/Documentation/control-dependencies.txt 及 tools/memory-model/litmus-tests/ 中的可执行验证用例。

一、总览:LKMM 定义的内存序操作三大类别

按照约束强度从强到弱,ordering.txt 将内核的内存序操作划分为三个顶层类别:

  1. 屏障(Barriers,又称 fences):屏障将 CPU 的部分或全部"先前操作"与部分或全部"后续操作"排序。
  2. 有序内存访问(Ordered memory accesses):这类操作根据其子类别,将自身与 CPU 的部分或全部先前访问、或部分或全部后续访问排序。
  3. 无序访问(Unordered accesses):顾名思义,本身不具备排序属性,除非它们与上述两个类别的操作交互。不过"现实世界总是不完美的",部分所谓的"无序"操作在个别特殊场合仍提供有限排序。

三大类别内部还可进一步细分:

顶层类别子类别
屏障全内存屏障、RMW 排序增强屏障、写内存屏障、读内存屏障、编译器屏障
有序内存访问Release 操作、Acquire 操作、RCU 读侧排序、控制依赖
无序访问无序标记操作、未标记的 C 语言访问

特别提醒(针对驱动开发者):许多排序原语在内核以CONFIG_SMP=n构建时完全不生成任何代码。因此,如果你在编写设备驱动程序,必须在CONFIG_SMP=n内核中也能正确排序对物理设备的访问,此时应使用为此目的提供的排序原语——例如用mb()代替smp_mb()(详见《Linux Device Drivers》一书及 LWN 相关文章)。这一要求在 include/asm-generic/barrier.h 中有直接体现:在!CONFIG_SMP分支中,smp_mb()smp_rmb()smp_wmb()一律被降级为barrier()

二、屏障(Barriers)

2.1 全内存屏障(Full Memory Barriers)

提供全排序语义的内核原语包括三类:

  • smp_mb()全内存屏障;
  • 返回值型 RMW 原子操作,且名字不以_acquire_release_relaxed结尾;
  • RCU 宽限期(grace-period)原语。

(1)smp_mb()的语义:它将该 CPU 的所有先前访问所有后续访问排序,且这种排序对所有 CPU 可见。换言之,所有 CPU 都会认同该 CPU 的任一先前动作先于其任一后续动作发生。例如:

WRITE_ONCE(x, 1); smp_mb(); // Order store to x before load from y. r1 = READ_ONCE(y);

所有 CPU 都会同意"对 x 的存储先于对 y 的读取"。原文特别强调:请为你的内存序原语添加注释——几个月后人们很难记起当初使用它的目的。

(2)提供全排序的 RMW 原子操作:指返回值非 void且名字不以_acquire/_release/_relaxed结尾的 RMW 原子操作,例如atomic_add_return()atomic_dec_and_test()cmpxchg()xchg()。当这些操作提供全排序时,它们把 CPU 的访问划分为三组:

  1. RMW 原子操作之前执行的全部代码;
  2. RMW 原子操作本身;
  3. RMW 原子操作之后执行的全部代码。

所有 CPU 都会同意:任意较高编号分区的操作先于较低编号分区……(原文表述为:任意给定分区中的操作先于编号更高的分区中的操作)。注意,条件型 RMW 操作(如cmpxchg()仅在成功时保证排序。

反例:返回值类型为 void 的 RMW 操作(如atomic_inc()atomic_dec())不保证任何排序;名字以_relaxed结尾的返回值型 RMW 操作(如atomic_cmpxchg_relaxed()atomic_xchg_relaxed())同样不保证排序;非 RMW 的返回值型原子操作(如atomic_read())也不提供全排序,它们属于后文"无序访问"一节。名字以_acquire_release结尾的返回值型 RMW 操作提供有限排序,将在后文详述。

(3)RCU 宽限期原语:包括synchronize_rcu()synchronize_rcu_expedited()synchronize_srcu()等。它们提供全排序,但开销比smp_mb()atomic_xchg()高出若干个数量级,且只能在可睡眠(sleepable)上下文中调用。因此它们通常被用于为 RCU 读侧临界区提供排序(如各自注释头所描述);当然,如果确实需要synchronize_rcu()与读者交互,额外依赖其全内存屏障语义也不会增加成本——只需认真加注释,否则"未来的你会恨现在的你"。

2.2 RMW 排序增强屏障(RMW Ordering Augmentation Barriers)

如 2.1 所述,atomic_inc()atomic_dec()这类非返回值 RMW 操作不保证任何排序。然而,包括 x86 在内的许多流行 CPU 家族本身就为这些原语提供了全排序。为了在所有架构上获得全排序,一个朴素的方案是在其后追加smp_mb()

WRITE_ONCE(x, 1); atomic_inc(&my_counter); smp_mb(); // Inefficient on x86!!! r1 = READ_ONCE(y);

这可行,但在 x86 上徒增开销(x86 的atomic_inc()本身就提供全排序)。smp_mb__after_atomic()正是为此而生:

WRITE_ONCE(x, 1); atomic_inc(&my_counter); smp_mb__after_atomic(); // Order store to x before load from y. r1 = READ_ONCE(y);

smp_mb__after_atomic()只在 atomic_inc() 实现不保证全排序的 CPU 上生成代码,因此在 x86 上零额外开销。smp_mb__*()家族还包括:

  • smp_mb__before_atomic():为无序 RMW 原子操作之前的访问提供全排序;
  • smp_mb__after_atomic():如上所示,为无序 RMW 原子操作之后的访问提供全排序;
  • smp_mb__after_spinlock():为成功获取自旋锁之后的访问提供全排序。注意spin_lock()总是成功,而spin_trylock()可能失败;
  • smp_mb__after_srcu_read_unlock():为srcu_read_unlock()之后的访问提供全排序。

重要实践:不要在smp_mb__*()原语与它所增强排序的那个操作之间放置代码,否则这段中间代码在不同 CPU 架构上的排序行为会不一致。

从实现上看,include/asm-generic/barrier.h 给出了通用后备定义:__smp_mb__before_atomic()__smp_mb__after_atomic()默认展开为__smp_mb(),架构(如 x86)可覆写为不生成指令的空实现;而在!CONFIG_SMP分支(barrier.h)中两者都降级为barrier()。此外,SMP 版本会包裹kcsan_mb()调用(barrier.h),以便 KCSAN 数据竞争检测工具感知屏障的存在。

2.3 写内存屏障(Write Memory Barrier)

内核的写内存屏障是smp_wmb()。若某 CPU 执行:

WRITE_ONCE(x, 1); smp_wmb(); WRITE_ONCE(y, 1);

则任意给定 CPU 都会看到"对 x 的写先于对 y 的写"。不过,你通常更适合使用 release 存储(见下文"Release 操作"一节)。

一个必须警惕的编译器陷阱smp_wmb()可能无法为未标记的 C 语言存储提供排序,因为基于 profile 的优化(PGO)可能判断被覆盖的值几乎总是等于新值,从而把x = 1y = 1合理地变换为:

if (x != 1) x = 1; smp_wmb(); // BUG: does not order the reads!!! if (y != 1) y = 1;

变换后屏障两侧变成了两个"读-判-写",smp_wmb()将不再对这些读操作排序。因此,若要用smp_wmb()配合未标记写,你必须确保构建内核所用的一切编译器(现在与未来)都不会执行这类变换——这正是官方文档反复强调"使用标记访问(WRITE_ONCE()等)"的根本原因。

2.4 读内存屏障(Read Memory Barrier)

内核的读内存屏障是smp_rmb()。若某 CPU 执行:

r0 = READ_ONCE(y); smp_rmb(); r1 = READ_ONCE(x);

则任意给定 CPU 都会看到"对 y 的读先于对 x 的读"。同样,你通常更适合使用 acquire 加载(见下文"Acquire 操作"一节)。

2.5 编译器屏障(Compiler Barrier)

内核的编译器屏障是barrier()。它禁止编译器执行跨过该点移动内存引用的代码移动优化,但不约束硬件内存排序。典型用途是防止编译器把代码搬过一个无限循环:

WRITE_ONCE(x, 1); while (dontstop) barrier(); r1 = READ_ONCE(y);

如果没有barrier(),编译器有权将WRITE_ONCE()移到循环之后;当中断处理器终止该循环时,这种代码移动可能造成问题。另一种做法是对dontstop的加载使用READ_ONCE()

值得注意的是,前文讨论的所有屏障,其实现内部都使用了barrier()或其底层等价物——这一点在 include/asm-generic/barrier.h 中清晰可见:smp_mb()定义为do { kcsan_mb(); __smp_mb(); } while (0),而__smp_mb()的弱定义在非 SMP 下就是barrier(),即使在某些 SMP 架构上,__smp_mb()也常内嵌barrier()以避免编译器重排硬件屏障指令。

三、有序内存访问(Ordered Memory Accesses)

3.1 Release 操作

Release 操作包括smp_store_release()atomic_set_release()rcu_assign_pointer()以及名字以_release结尾的返回值型 RMW 操作。这些操作将自身的存储与该 CPU 的全部先前内存访问排序。相比显式屏障,Release 操作通常兼具更好的可读性与性能。

例如,用smp_store_release()替换 2.3 节的smp_wmb()示例可以省去一行:

WRITE_ONCE(x, 1); smp_store_release(&y, 1);

更重要的是,smp_store_release()让并发算法的各片段更易"对上号":被 release 存储的变量(此处是y),通常会在算法的其他部分被某个 acquire 操作读取。

性能优势示例:假若上面的例子不是写x而是读x,则smp_wmb()无法保证排序,只能退而使用smp_mb()

r1 = READ_ONCE(x); smp_mb(); WRITE_ONCE(y, 1);

smp_mb()的开销往往远高于smp_store_release()(后者仍能提供所需的 x 对 y 的排序)。在 x86 上,smp_store_release()版本可能只编译为一条普通 load 加一条普通 store;而smp_mb()则编译为一条昂贵的排序指令。

Release 操作的具体成员:

  • 存储类:除smp_store_release()外,还有atomic_set_release()atomic_long_set_release()
  • RCU 的rcu_assign_pointer():与smp_store_release()等价,但有三点区别:(1) 它接收"被赋值的指针"而非"指向该指针的指针";(2) 它设计上与rcu_dereference()等配合使用,而非smp_load_acquire();(3) 在 sparse 静态检查中会校验被赋值的确实是一个 RCU 保护的指针;
  • 名字以_release结尾的返回值型 RMW 操作:如atomic_fetch_add_release()cmpxchg_release()。注意:release 排序只针对 RMW 操作中的存储部分,不针对其加载部分;且cmpxchg_release()这类条件操作仅在成功时保证排序。

从实现看,include/asm-generic/barrier.h 的通用后备实现__smp_store_release(p, v)为:先执行__smp_mb(),再执行WRITE_ONCE(*p, v),并带有compiletime_assert_atomic_type(*p)编译期类型断言;x86 等强排序架构则会覆写为更轻量的实现。

3.2 Acquire 操作

Acquire 操作包括smp_load_acquire()atomic_read_acquire()以及名字以_acquire结尾的返回值型 RMW 操作。这些操作将自身的加载与该 CPU 的全部后续内存访问排序,同样通常比显式屏障性能更好、可读性更高。用smp_load_acquire()替换 2.4 节smp_rmb()示例可省去一行:

r0 = smp_load_acquire(&y); r1 = READ_ONCE(x);

smp_store_release()类似,这也有助于读者通过查找"存储到 y 的smp_store_release()"来串联并发算法的不同片段。此外,smp_load_acquire()smp_rmb()更进一步:它同时排序后续的存储与后续的加载

Acquire 操作的具体成员:

  • 加载类:除smp_load_acquire()外,还有atomic_read_acquire()atomic64_read_acquire()
  • 名字以_acquire结尾的返回值型 RMW 操作:如atomic_xchg_acquire()atomic_cmpxchg_acquire()。注意:acquire 排序只针对 RMW 操作中的加载部分,不针对其存储部分;atomic_cmpxchg_acquire()这类条件操作仅在成功时保证排序。

include/asm-generic/barrier.h 中__smp_load_acquire(p)的通用后备实现为:先用READ_ONCE(*p)读取,再执行__smp_mb(),同样带类型断言。

Acquire/Release 配对示例:对称之美在于,acquire 操作常与 release 操作配对使用。考虑task0()task1()并发执行的例子:

void task0(void) { WRITE_ONCE(x, 1); smp_store_release(&y, 1); } void task1(void) { r0 = smp_load_acquire(&y); r1 = READ_ONCE(x); }

xy初始均为零,则要么r0的最终值为零,要么r1的最终值为一——这正是消息传递(message passing)模式所需的排序保证。

该模式在 LKMM 的 litmus 测试中有直接的可执行验证:tools/memory-model/litmus-tests/MP+pooncerelease+poacquireonce.litmus 声明"Result: Never",即exists (1:r0=1 /\ 1:r1=0)的坏结果永远不会出现,从而证明smp_store_release()smp_load_acquire()对消息传递模式提供了充分排序。与之对照,tools/memory-model/litmus-tests/SB+fencembonceonces.litmus(store-buffering 模式)则演示了必须使用smp_mb()全屏障才能排除exists (0:r0=0 /\ 1:r0=0)的坏结果——"锁与 RCU 也可以,但其他手段大多不行"。

3.3 RCU 读侧排序(RCU Read-Side Ordering)

该类别包括rcu_read_lock()rcu_read_unlock()等读侧标记,以及rcu_dereference()srcu_dereference()等指针遍历原语。

与锁原语和 RMW 原子操作相比,RCU 读侧临界区的标记开销极低,因为它们只与对应的宽限期原语交互。例如rcu_read_lock()/rcu_read_unlock()synchronize_rcu()synchronize_rcu_expedited()call_rcu()交互:若某次synchronize_rcu()无法证明自己先于某次rcu_read_lock()开始,那么它必须阻塞到与之匹配的rcu_read_unlock()为止。更多细节可参见synchronize_rcu()的 docbook 头注释及仓库内 Documentation/RCU 目录。

RCU 的指针遍历原语(rcu_dereference()srcu_dereference())将其加载(必须是指针)与该 CPU 后续所有地址由该加载值计算得到的内存访问排序——这被称为从返回指针对后续访问存在地址依赖(address dependency)

对某 RCU 保护指针的rcu_dereference()调用,通常与对同一指针的rcu_assign_pointer()配对,其方式与smp_load_acquire()/smp_store_release()的配对如出一辙。二者常常被封装进其他 API——例如 include/linux/rculist.h 中定义的 RCU 链表 API 成员。若指针值在rcu_dereference()返回后、再次rcu_dereference()之前被算术修改,请务必阅读 Documentation/RCU/rcu_dereference.rst。仓库中的 tools/memory-model/litmus-tests/MP+onceassign+derefonce.litmus 即为验证用例:它声明exists (1:r0=x /\ 1:r1=0)(读者看到新指针却读到初始化前的垃圾值)"Never" 出现,证明rcu_assign_pointer()rcu_dereference()足以保证 RCU 读者在遍历含新插入元素的结构时不会看到未初始化的内容。

3.4 控制依赖(Control Dependencies)

控制依赖从一次标记加载READ_ONCE()或更强)出发,经由一个if条件,延伸到仅在该if语句某一分支中执行的标记存储WRITE_ONCE()或更强)。之所以叫"控制依赖",是因为它由比较、条件分支等控制流指令中介。简言之:当READ_ONCE()WRITE_ONCE()之间存在if条件时,可以用控制依赖强制二者间的排序。经典示例:

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

此时所有 CPU 都会看到"对 a 的读先于对 b 的写"。

然而,控制依赖极易被编译器优化摧毁,任何使用都必须通盘考虑构建内核所用的全部编译器。官方为此单列了一份深度文档:tools/memory-model/Documentation/control-dependencies.txt,其中列举了若干关键陷阱,择要如下:

  • 只排序后来的存储:load-load 控制依赖不保证排序,必须显式加读屏障:
    q = READ_ONCE(a); if (q) { smp_rmb(); p = READ_ONCE(b); }

    因为部分 CPU 允许猜测b的加载结果;而存储不会被猜测,所以 load-store 控制依赖(通常)成立。

  • READ_ONCE()WRITE_ONCE()缺一不可:没有READ_ONCE(),编译器可能把对a的加载与其他加载融合;没有WRITE_ONCE(),编译器可能把对b的存储与其他存储融合,甚至把存储变换为"加载+检查+存储",而编译器生成的这个加载不受控制依赖排序。
  • 编译器可能证明a恒非零,从而合法地消去if
    q = a; b = 1; /* BUG: Compiler and CPU can both reorder!!! */
  • 两个分支存储相同值时,编译器会把存储提升到if之外,从而消灭控制依赖;此时必须改用显式内存序(如smp_store_release())或smp_mb()。仅靠barrier()不够。
  • 对局部变量q的运算可能让编译器猜出值(如if (q % MAX)MAX在编译期为 1),从而再次消去条件分支;必要时可用BUILD_BUG_ON(MAX <= 1)施加编译期约束。
  • 布尔短路求值同样危险:if (q || 1 > 0)中第二个条件恒真,编译器可将整个if消去。
  • 控制依赖只作用于if的分支内部,不排序if语句之后的代码;因为编译器可能把两分支的存储编译为条件移动指令(cmov),使依赖只延伸到 cmov 及其依赖的存储。

control-dependencies.txt的总结要点包括:控制依赖只能排序"先前加载 vs 后续存储",若需其他排序请用smp_load_acquire()smp_store_release()或(先前存储 vs 后续加载时)smp_mb();两分支同值存储必须显式排序;至少需要一个涉及先前加载的运行时条件分支;控制依赖可与其它屏障正常配对;控制依赖不提供多拷贝原子性(multicopy atomicity),若需所有 CPU 就某存储与所有访问的排序达成一致,请用smp_mb();编译器不理解控制依赖,保护它们是你的责任。

四、无序访问(Unordered Accesses)

4.1 无序标记操作(Unordered Marked Operations)

对不同变量的无序操作就是无序的。但若一组 CPU 对单个变量施加这些操作,所有 CPU 会就操作顺序达成一致。当然,无序标记访问也可被本文前述机制约束。它们分三类:

  • 标记写:如WRITE_ONCE()atomic_set()。这些原语要求编译器按预期执行顺序发出对应存储指令,从而抑制一系列破坏性优化;但不提供任何硬件排序保证——事实上,许多 CPU 会很乐意重排标记写(彼此之间,或与其它无序操作之间),除非这些操作针对同一变量。
  • 标记读:如READ_ONCE()atomic_read()。语义与标记写对称:约束编译器,不约束硬件。
  • 无序 RMW 原子操作:指非返回值型且名字不以_acquire/_release结尾的 RMW 操作,以及名字以_relaxed结尾的返回值型 RMW 操作,例如atomic_add()atomic_or()atomic64_fetch_xor_relaxed()。它们仍保证 RMW 的原子性——五个并发的atomic_inc()施加于同一变量,可靠地使其值增加五;但许多 CPU 会重排它们与彼此、与其它无序操作之间的顺序。该类别可借助 2.2 节的smp_mb__before_atomic()/smp_mb__after_atomic()高效地获得排序。

简言之:除非全部作用于单一变量,或被本文前述操作约束,否则这些操作可以被自由重排。

4.2 未标记的 C 语言访问(Unmarked C-Language Accesses)

未标记的 C 语言访问,是指对普通变量的普通访问——即既非volatile、也非 C11 原子变量的变量。这类访问不提供任何排序保证,也不保证访问的"原子性"。例如编译器可能(也确实会)把一次普通存储拆分为多次更小的存储;当另一 CPU 在存储执行期间加载同一变量,可能看到新旧值混杂(mashup)的结果。

未标记访问天然无序,并受无数编译器优化影响,其中许多会破坏你的并发代码。为共享变量使用未标记访问虽有可能,但需要持续的高度谨慎。barrier()原语及本文讨论的各种排序原语可提供帮助;但必须再次强调:使用未标记访问不仅要盯着自己的代码,还要盯着可能用来构建它的所有编译器——它们可能把一系列加载合并为一次加载、把一系列存储合并为一次存储,甚至把一次存储拆成多次更小的存储。

不过,存在几种可以放心对共享变量使用未标记访问的方式:

  • 用特定锁守护对该变量的所有访问,使该变量永不存在并发的冲突访问(当 (1) 对某变量的并发访问中至少有一个是未标记访问,且 (2) 其中至少有一个是写(无论是否标记)时,即构成"冲突访问");
  • 与上相同,但改用读写锁、顺序锁(seqlock)等其它同步原语;
  • 用锁等手段确保对某变量的所有并发访问都是读;
  • 将该变量仅用于统计或启发式用途,容忍偶尔出现的错误值;
  • 将访问的变量声明为 C11 原子类型;
  • 将访问的变量声明为volatile

如果你需要"更危险地活着",请务必花时间理解编译器(LWN 上有两篇著名的相关文章:《Who's afraid of a big bad optimizing compiler?》与《Calibrating your fear of big bad optimizing compilers》)。用得妥当,未标记访问可以降低快速路径的开销;代价是持续的高度警觉——每当新版本编译器发布、新优化被启用,都要重新审视代码。

五、速查表与可执行验证

5.1 排序能力速查表

cheatsheet.txt 以表格形式总结了各类操作的排序能力。表中行代表操作类别,列代表"先前操作/后续操作"对,Y表示提供排序,a表示在存在中间 RMW 原子操作时提供排序,C表示排序是累积的(cumulative),P表示排序会传播(propagates):

操作先前操作后续操作
Relaxed store对自身(Self)对同变量的后续访问(SV)
Relaxed load对自身依赖读 DR、依赖写 DW、同变量 SV
Relaxed RMW 操作对自身DR、DW、SV
rcu_dereference()对自身DR、DW、SV
成功的*_acquire()读 R后续读 R、依赖读 DR、依赖写 DW、RMW、SV
成功的*_release()累积 C、自身、读 R、写 W、RMW写 W、SV
smp_rmb()读 R读 R、DR
smp_wmb()写 W写 W、DW
smp_mb()synchronize_rcu()C、P、自身、R、W、RMW自身、R、W、DR、DW、RMW、SV
成功的全排序非 void RMWC、P、自身、R、W、RMW自身、R、W、DR、DW、RMW、SV
smp_mb__before_atomic()C、P、自身、R、W依赖中间 RMW 的 a、a、a、a、SV
smp_mb__after_atomic()C、P、依赖中间 RMW 的 a、a、W自身、R、W、DR、DW、RMW、SV

其中 "Relaxed" 指READ_ONCE()WRITE_ONCE()*_relaxed()RMW、失败的 RMW、atomic_inc()这类非返回值 RMW,以及atomic*_read()/atomic*_set()家族。该表是排查并发 bug 时的高效索引。

5.2 用 LKMM 工具链验证排序

仓库 tools/memory-model/ 目录本身就是一套可运行的 LKMM 工具链,包含:

  • linux-kernel.bell:定义事件的分类(读、写、RMW、屏障等);
  • linux-kernel.cat:核心排序公理(acyclicity、sc 等)与约束;
  • linux-kernel.def:各类原语到内存模型事件的映射;
  • lock.cat:锁相关排序规则;
  • scripts/:运行 litmus 测试的辅助脚本;
  • litmus-tests/:大量可执行测试,覆盖屏障、锁、RCU、控制依赖、release/acquire 等模式。

例如MP+pooncerelease+poacquireonce(消息传递 + release/acquire)、SB+fencembonceonces(store-buffering + 全屏障)、MP+onceassign+derefonce(RCU 指针发布)等,每个文件头部都声明了期望结果(Result: Never / Sometimes),坏结果以exists (...)形式给出。你可以利用该工具链对自己编写的并发模式进行机械化验证,而不是仅凭直觉判断——这正是 LKMM 相对纯文档讲解的独特价值。

结语

内存序是 Linux 内核并发编程中最容易出错、也最需要精确理解的主题之一。ordering.txt 按"屏障 → 有序访问 → 无序访问"的强度层级,为开发者提供了一张清晰的选型地图:需要最强约束时用全屏障或全排序 RMW;需要对称的发布/消费语义时优先 release/acquire 配对;RCU 场景使用rcu_assign_pointer()/rcu_dereference();能不用屏障时则尽量依赖控制依赖——但务必先读透control-dependencies.txt的警示。无论选择哪条路径,记住三条铁律:优先使用标记访问(READ_ONCE()/WRITE_ONCE()及更强原语)始终考虑所有可能编译内核的编译器为每个内存序原语写清注释。结合本仓库的速查表与 litmus 测试,你可以在动手写代码之前就完成排序语义的形式化验证。

【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 18:46:09

Go函数调用瞬间:栈帧构造、栈拷贝与逃逸分析全解析

写 Go 写了几年&#xff0c;我越来越觉得&#xff0c;搞懂一次函数调用里发生的事&#xff0c;是区分“会写 Go”和“懂 Go”的一条分水岭。表面上看&#xff0c;你只是在代码里敲了一行f(x)&#xff0c;但底层牵动的东西一点不少&#xff1a;栈帧&#xff08;stack frame&…

作者头像 李华
网站建设 2026/9/16 18:46:00

信创OA系统集成动易控件处理Word公式技术解析

1. 信创OA系统与动易控件集成背景在国产化信息技术应用创新&#xff08;信创&#xff09;背景下&#xff0c;OA系统作为企业核心办公平台&#xff0c;面临文档处理能力国产化适配的关键需求。动易控件作为国内主流的文档处理组件&#xff0c;其与OA系统的深度集成需要解决以下核…

作者头像 李华
网站建设 2026/9/16 18:45:55

Docker容器停止与删除的本质区别及安全操作指南

1. 这不是“删文件”&#xff0c;而是精准控制容器生命周期的日常操作Docker 容器不是 Windows 里右键删除的普通文件夹&#xff0c;也不是双击关闭的桌面程序。它是一套有明确状态、有资源绑定、有依赖关系的运行时实体。你看到的“停止”和“删除”&#xff0c;背后其实是两套…

作者头像 李华
网站建设 2026/9/16 18:45:43

Windows上Unity构建iOS真机打包全流程:证书签名与Xcode部署

在Windows上用Unity做iOS真机打包测试&#xff0c;这个话题在我被问到的频率高得离谱。很多人一开始都以为“Unity不是跨平台吗&#xff1f;我点一下Build不就能出包了&#xff1f;”结果折腾半天发现&#xff0c;Unity在Windows上压根产不出iOS的安装包&#xff0c;更别说直接…

作者头像 李华
网站建设 2026/9/16 18:44:14

青少年心理健康问题:家长需要知道的十件事-中国心理学会心理咨询师水平评价-长春心理咨询培训机构

青少年心理健康问题&#xff1a;家长需要知道的十件事中国心理学会心理咨询师水平评价-心理咨询培训机构 近年来青少年心理健康问题越来越受到社会关注。作为家长&#xff0c;你了解多少关于青少年心理健康的知识&#xff1f;今天整理了家长较为需要知道的十件事&#xff0c;帮…

作者头像 李华