当前位置: 首页 > news >正文

Linux 内核内存屏障(中文译文)

============================
Linux 内核内存屏障(中文译文)

作者:David Howells dhowells@redhat.com
Paul E. McKenney paulmck@linux.vnet.ibm.com

说明:本译文意在忠实呈现 Linux 内核文档 memory-barriers.txt 的技术内容与结构。为便于阅读,保留了重要术语(如 ACQUIRE/RELEASE、mb/rmb/wmb、READ_ONCE/WRITE_ONCE、DMA、MMIO、RCU 等)的英文形式,并在需要时给出中文释义。示例中的伪代码、时序图与要点总结均以更适合中文读者的方式表述。

目录

  • 抽象的内存访问模型
  • 什么是内存屏障
    • 内存屏障的种类
    • 内存屏障不保证什么
    • 数据依赖屏障
    • 控制依赖
    • SMP 屏障配对
    • 屏障序列示例
    • 读屏障与加载推测
    • 传递性(累积性)
  • 显式内核屏障
    • 编译器屏障、READ_ONCE/WRITE_ONCE
    • CPU 内存屏障(mb/rmb/wmb 等)
    • 高级屏障(smp_store_mb、smp_mb__before/after_atomic 等)
    • 一致性内存与 DMA 屏障(dma_rmb/dma_wmb)
    • MMIO 写屏障(mmiowb)
  • 隐式内核屏障
    • 加锁原语(ACQUIRE/RELEASE)
    • 禁用/启用中断
    • 睡眠与唤醒(wait/wake)
    • 其他杂项函数(如 schedule)
  • CPU 间加锁的屏障效应
    • 锁 vs 内存访问
    • 锁 vs I/O 访问(需 mmiowb)
  • 在哪里需要内存屏障
    • 处理器间交互
    • 原子操作
    • 访问设备(MMIO/一致性内存)
    • 中断
  • 假定的最低执行顺序模型(简述)
  • CPU 缓存的影响
    • 缓存一致性
    • 与 DMA/与 MMIO 的一致性问题
    • CPU 行为与架构差异(含 Alpha)
  • 示例(如环形缓冲区)
  • 参考资料

抽象的内存访问模型

在多核系统中,每个 CPU 执行程序并发起内存访问;系统其他部分(内存、其他 CPU、设备)通过接口观察这些访问的“效果”。编译器与 CPU 可在不破坏单核语义的前提下重排指令与内存操作,因此不同 CPU 之间看到的顺序可能不同。

关键点:

  • 独立的内存访问可能以几乎任意顺序被其他参与者观察到。
  • 数据依赖可保证同一 CPU 内某些加载的顺序,但跨 CPU 时仍需屏障。
  • 设备(通过 MMIO)通常要求严格的访问顺序;CPU/编译器的重排会导致设备操作失败。

什么是内存屏障

内存屏障(memory barrier)用于对屏障两侧的内存操作施加可被其他 CPU/设备观察到的“部分顺序”。它们抑制重排、推测与合并,使并发代码对交互的时序具备可控性。

内存屏障的种类

典型类型如下:

  • 写屏障(Write barrier,wmb/smp_wmb):保证屏障之前的所有写(store)先于屏障之后的所有写被其他参与者观察。它只约束写,不约束读。
  • 数据依赖屏障(read_barrier_depends/smp_read_barrier_depends):当第二次加载的地址或内容依赖第一次加载的结果时,保证被依赖的数据在后续加载前可见。它只约束存在“真实数据依赖”的加载,不影响写或独立加载。
  • 读屏障(Read barrier,rmb/smp_rmb):保证屏障之前的所有读(load)先于屏障之后的所有读被观察。它隐含数据依赖屏障。
  • 通用屏障(Full/General barrier,mb/smp_mb):同时约束读与写,即屏障前的所有读写先于屏障后的所有读写被观察。它隐含读与写屏障。
  • ACQUIRE/RELEASE:单向屏障。ACQUIRE 保证其后的访问在它之后出现;RELEASE 保证其前的访问在它之前出现。二者通常配对使用(如锁的加/解)。ACQUIRE+RELEASE 并非完整屏障,但在临界区层面保证“之前的临界区完成后,后继临界区的可见性”。

内存屏障不保证什么

内核屏障不保证:

  • 屏障完成即等同“屏障之前的访问已对所有参与者完成”。它只在本 CPU 的提交序列上画一条线,合格类型的访问不得跨线重排。
  • 单侧屏障能让其他 CPU 自动“按正确顺序观察”你的访问;通常需要配对屏障(见下)。
  • 中间硬件不重排;总线与缓存可能仍以自身协议传播与重排。

数据依赖屏障

当第二次加载依赖第一次加载的结果(如先读指针、再读该指针指向的数据)时,若不插入数据依赖屏障,另一 CPU 对数据的更新可能在你“看到指针更新”之后仍不可见,导致读到旧值。DEC Alpha 等架构上这种反直觉行为可出现。解决:在“读指针”与“读指针指向内容”之间放数据依赖屏障或更强屏障。

控制依赖

若第二次加载(或存储)仅通过分支条件依赖第一次加载(而不是地址本身依赖),属于控制依赖。加载-加载的控制依赖需要读屏障(而非数据依赖屏障)才能可靠排序。编译器可能消除条件或合并访问;必须使用 READ_ONCE/WRITE_ONCE 防优化,并在必要处使用显式屏障(如 smp_store_release)。控制依赖不具传递性。

SMP 屏障配对

当涉及 CPU-CPU 交互时需配对:

  • 写屏障通常应与对端的读屏障或数据依赖屏障配对。
  • 通用屏障可与多数屏障配对,但不具传递性。
  • ACQUIRE 通常配 RELEASE。
    配对的意图是确保一侧的“先后关系”能被另一侧以相应的顺序观察到。

屏障序列示例

示例展示:

  • 写屏障把“屏障前的一组写”排在“屏障后的一组写”之前。
  • 数据依赖屏障把“依赖加载”的可见性与顺序对齐到另一 CPU 的更新之后。
  • 读屏障保证“先读 B 后读 A”时,若对端在写屏障后写了 B 与 A,则第二次读 A 将看到对应更新。

读屏障与加载推测

CPU 可能在分支或长指令(如除法)期间对某加载进行推测。读屏障或数据依赖屏障会在屏障处“重新审视”该推测值:若没有失效/更新,推测值可以继续使用;否则强制重新加载以获得最新值。

传递性(累积性)

通用屏障在内核中保证跨 CPU 的传递性:若 CPU2 在通用屏障配对下观察到 X 已更新、Y 未更新,则 CPU3 对 X 的读不应回退。仅读/写屏障不保证累积性;需要通用屏障确保“全系统顺序一致”。

显式内核屏障

编译器屏障与 READ_ONCE/WRITE_ONCE

  • barrier():编译器屏障,阻止编译器跨越屏障重排内存访问(对 CPU 无直接约束)。
  • READ_ONCE()/WRITE_ONCE():防止编译器合并、消除、撕裂或重排针对某一变量的访问;在并发语境下保持单变量访问的“原子性”与“可见性假设”。用于防止多种“看似合理但有害”的优化。

CPU 内存屏障(必选与 SMP 条件)

  • 必选:mb()/wmb()/rmb()/read_barrier_depends()。对 UP 也有意义,尤其用于 I/O 顺序。
  • SMP 条件:smp_mb()/smp_wmb()/smp_rmb()/smp_read_barrier_depends()。在 UP 上退化为编译器屏障;在 SMP 上提供跨 CPU 的顺序约束。

高级屏障与原子操作辅助

  • smp_store_mb(var, value):存储后插入完整屏障。
  • smp_mb__before_atomic()/smp_mb__after_atomic():与不返回值的原子操作(如原子加减、位操作)配合,保证相邻访问相对于其他 CPU 的顺序可感知,常用于引用计数与对象生命周期管理。
  • lockless_dereference():围绕数据依赖屏障的无锁指针获取工具,适合非 RCU 管理生命周期的场景。

一致性内存与 DMA 屏障(共享内存)

  • dma_rmb()/dma_wmb():约束 CPU 与具 DMA 能力设备共享内存的读写顺序。示例:只有在设备释放描述符所有权后再读数据(dma_rmb);在把数据写回描述符后再把所有权交给设备(dma_wmb)。
  • 与 wmb() 配合:在向设备 MMIO 写“门铃”之前,确保共享内存写入完成。

MMIO 写屏障(设备寄存器)

  • mmiowb():对弱序 I/O 区域的写进行部分有序化。在 NUMA 或桥设备场景下,多 CPU 释放同一锁前分别写 MMIO,若不使用 mmiowb(),可能在桥上交错导致设备看到错序。mmiowb() 保障“同一锁保护下的写”在释放前一致地提交。

隐式内核屏障

加锁原语(ACQUIRE/RELEASE)

  • 自旋锁/读写自旋锁/互斥量/信号量/读写信号量都具 ACQUIRE/RELEASE 语义。
  • ACQUIRE:其后的访问出现在 ACQUIRE 之后;之前的访问可能渗入 ACQUIRE 之后(单向)。
  • RELEASE:其前的访问出现在 RELEASE 之前;之后的访问可能渗入 RELEASE 之前(单向)。
  • ACQUIRE 与 RELEASE 并非完整屏障;不要将“加锁后立即解锁”视作 mb。
  • 某些失败的获取(未能拿到锁)不隐含屏障。

禁用/启用中断

  • 禁用中断与启用中断仅作为编译器屏障;若需要内存或 I/O 屏障,需显式加入。

睡眠与唤醒(wait/wake)

  • set_current_state() 在设置任务状态后执行 smp_store_mb()(完整屏障),保证“先设置状态,再观察事件标志”的顺序。
  • wake_up()/wake_up_process() 在实际唤醒时隐含写屏障,排序“事件指示写”与“设置 TASK_RUNNING”。
  • 若睡眠方需要在看到事件后读取多个数据项的顺序(相对于唤醒方的写),双方需显式插入 smp_wmb/smp_rmb 以对齐可见性。

其他杂项函数

  • schedule() 等调度相关函数隐含完整内存屏障。

CPU 间加锁的屏障效应

锁 vs 内存访问

  • 在 SMP 上,不同 CPU 的临界区访问相互可见,但顺序受到各自 ACQUIRE/RELEASE 的约束。第三方 CPU 不会观察到“释放锁后的写”排在“获取锁前的写”之前(相对于该锁)。

锁 vs I/O 访问(需 mmiowb)

  • NUMA/桥设备可能导致两个 CPU 在同一锁保护下的 MMIO 写在桥上交错。释放自旋锁前调用 mmiowb() 可强制同一锁内的 MMIO 写顺序一致。若同一设备在写后紧随读(readl),读可强制写完成,从而免 mmiowb。

在哪里需要内存屏障

处理器间交互

  • 多 CPU 访问同一数据集时,若不使用锁,需要以屏障保证关键读写的先后与可见性。例如在信号量慢路径中,唤醒方必须以“读 next 指针、读 task 指针、mb、清 task、唤醒”的顺序进行,以防在清 task 后被唤醒方破坏栈导致读取 next 失败。

原子操作

  • 修改内存并返回旧/新状态的原子操作(如 xchg、atomic_return、test_and_bit、cmpxchg 成功时等)隐含 SMP 条件的完整屏障(除特殊锁原语外),适合实现 ACQUIRE/RELEASE 或引用计数语义。
  • 不隐含屏障的原子(atomic_set、set_bit/clear_bit/change_bit、atomic_add/sub/inc/dec 等)在实现 RELEASE 或构造锁场景可能需要显式 smp_mb__before_atomic 等。

访问设备(MMIO/一致性内存)

  • 设备往往要求严格顺序;CPU 与编译器可能重排、合并或融合内存访问,导致设备看到错序。需结合 wmb/rmb/mb、dma_wmb/dma_rmb、mmiowb 与 READ_ONCE/WRITE_ONCE 保证序与可见性。

中断

  • 进程级与中断处理在共享数据上的交互常需 READ_ONCE/WRITE_ONCE;在需要时以屏障确保“先写消息,再置标志;中断侧先读标志,再读消息”的顺序。

假定的最低执行顺序模型(简述)

内核文档在此部分描述了在缺乏更强架构保证时,内核可依赖的最低排序模型:独立读写可能重排;依赖访问在本 CPU 内保持顺序;跨 CPU 的顺序需通过显式屏障或锁来建立;通用屏障提供跨 CPU 的传递性。

CPU 缓存的影响与一致性

缓存一致性

  • 大多数现代架构在 CPU 之间提供缓存一致性协议,使得对同一缓存行的更新可传播。但传播的时序与顺序可能与程序期望不一致,需屏障来约束可见性。

与 DMA 的一致性

  • 设备通过 DMA 直接访问内存;CPU 的缓存可能需要刷写/失效以与设备视图一致。dma_rmb/dma_wmb 可在共享描述符/队列的所有权转移与数据读写之间建立正确的顺序。

与 MMIO 的一致性

  • MMIO 通常不参与 CPU 缓存一致性;需 wmb/rmb/mb 与 mmiowb 来约束寄存器写的顺序与提交点,防止桥或设备端观察到交错或乱序。

架构差异与 CPU 行为

  • 不同架构对推测、重排与缓存行为的下限保证不同。Alpha 等架构尤其宽松,数据依赖屏障在其上必需;而在多数其它架构上,数据依赖屏障可能为空操作。编写可移植并发代码时应以“最低保证”设计。

示例:环形缓冲区(简述)

  • 生产者写入数据与更新写指针之间需 wmb/mb,消费者读指针与读数据之间需 rmb/mb(或 ACQUIRE/RELEASE 组合),以确保“先有数据,再更新指针;先观察指针,再读取数据”的顺序与可见性。若指针以指针-数据的形式组织,消费者端的数据依赖屏障也可能适用。

参考资料

  • Documentation/atomic_ops.txt:原子操作的语义与隐含屏障。
  • Documentation/DMA-API.txt 与 DMA-API-HOWTO.txt:DMA 与一致性内存的使用与屏障。
  • Documentation/PCI/pci.txt:PCI 相关一致性与访问次序问题。
  • Documentation/DocBook/deviceiobook.tmpl:设备 I/O 顺序与屏障讨论。
  • Peter Sewell 等人的内存模型试验(如 LB/WWC):理解控制依赖与传递性限制。

术语对照(便于查阅)

  • barrier:编译器屏障
  • mb/rmb/wmb:必选 CPU 内存屏障(全/读/写)
  • smp_mb/smp_rmb/smp_wmb:SMP 条件屏障(全/读/写)
  • read_barrier_depends/smp_read_barrier_depends:数据依赖屏障(必选/SMP 条件)
  • smp_store_release/smp_load_acquire:释放/获取操作(单向屏障)
  • READ_ONCE/WRITE_ONCE:防编译器优化的单次访问原语
  • dma_rmb/dma_wmb:一致性内存的读/写屏障(CPU 与设备共享内存)
  • mmiowb:MMIO 写屏障(在释放锁前强制同锁内 MMIO 写顺序)
  • ACQUIRE/RELEASE:获取/释放(锁语义)
  • MMIO:内存映射 I/O
  • RCU:Read-Copy Update(读-复制-更新),常与数据依赖屏障配合

结语

内存屏障是并发内核代码的基石之一。正确选择与配对屏障,结合 READ_ONCE/WRITE_ONCE、防止控制依赖被编译器优化掉、理解设备与缓存一致性,是保证跨 CPU 与与设备交互时序可见性与正确性的关键。编写并发代码时,务必以最低保证为基线,必要时使用通用屏障以获得传递性。

http://www.dtcms.com/a/540551.html

相关文章:

  • “二分查找” 咋用?像 “查字典翻页码”,3 步找到目标值​
  • 在Ubuntu中使用Docker打包程序(Conda, pip)
  • 网站优化软件费用大连网站推广优化
  • 31_AI智能体工具插件之增强LangChain注册工具构建高效可控的AI工具生态
  • 怎么做自建站wordpress 导航加图标
  • 解决uni-app通用上传与后端接口不匹配问题:原生上传文件方法封装 ✨
  • 管廊建设网站线上推广网络公司
  • 汽车交互式系统专利拆解:VR/AR 画面生成与挡风玻璃异步转换的流畅性测试
  • Python爬虫实战:中信标普 50 指数数据获取与趋势分析
  • 浦江网站建设站酷app
  • 什么是技术架构、数据架构、业务架构、应用架构、产品架构和项目架构?
  • LLaMA-Factory 集成了哪些超参数调优框架?及 Optuna + Weights Biases + TensorBoard对比分析
  • 【软考架构】案例分析:MongoDB 如何存储非结构化数据以及其矢量化存储的优点。
  • 网络共享总失败?先检查是否有计算机名冲突
  • 最好用的企业网站cms湘潭网站建设工作室
  • 在网站后台做网页河南省工程建设协会网站
  • 【k8s】Deployment、StatefulSet、DaemonSet
  • 优购物官方网站订单查询烟台建设科技网站
  • qrc机制——Qt
  • [尚硅谷-尚庭公寓0-90做了部分]
  • 外资企业可以在中国境内做网站吗自己做网站买东西
  • 百度脑图网站建设流程图php网站模板源码下载
  • TDengine 数学函数 SQRT 用户手册
  • 【Qt C++ QSerialPort】QSerialPort fQSerialPortInfo::availablePorts() 执行报错问题解决方案
  • 鸿蒙ArkUI布局与样式进阶(十四)——剩余参数 · 展开运算符 · 接口继承 · 接口实现 · 泛型全面讲解
  • 【# Python 离线安装:把 pip 源设为本地目录】
  • 在pycharm中install不上需要的包
  • 国外工业设计网站建站系统软件有哪些
  • 做网站毕业设计华建河北住房和城乡建设厅网站
  • 公司网站免费建站西安网站工作室