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

Linux + arm 内存屏障

ARM 硬件层的屏障指令

  • DMB (Data Memory Barrier):保证在它之前的内存访问(符合给定域/类型)在它之后的内存访问之前对可见性排序。常用域:ish(Inner Shareable),sy(system-wide,最强)。

  • DSB (Data Synchronization Barrier):比 DMB 更强,等到之前的访问“完成”才继续执行下一条指令;常用于设备寄存器编程后需要确保“已生效”的情景。

  • ISB (Instruction Synchronization Barrier):刷新取指流水,常用于修改系统寄存器后,或自修改代码等需要让新指令可见的场景。

  • Acquire/Release 指令(ARMv8):LDAR(获取/读-获取),STLR(释放/写-释放),提供更精细的有序性而不必全栈 DMB

Linux 内核里的内存屏障原语(SMP 语义)

这些在 SMP 有效,在 UP 可能是 no-op(但保持可读性与可移植性):

  • smp_mb():全栅栏(读写都排序)。

  • smp_rmb() / smp_wmb():仅读-读/写-写排序(以及与相对方向的最小保障,取决于架构实现)。

  • smp_store_release(p, v) / smp_load_acquire(p):推荐!映射到 ARM 的 STLR/LDAR,开销更低,适合无锁队列/环形缓冲区。

  • READ_ONCE(x) / WRITE_ONCE(x, v):防止编译器优化/拆分访问,但提供跨 CPU 的排序;常与 acquire/release 或 smp_* 组合用。

  • 原子操作:大多自带适当的屏障语义(比如 atomic_xxx_return 通常是 full barrier),但具体要看接口文档;不要想当然。

锁类原语自带屏障:

  • spin_lock/unlock:进入/退出临界区相当于 acquire/release 屏障。

  • mutex_lock/unlockrcu_read_lock/unlock 等也带有已定义的顺序保证。

设备/MMIO 与 I/O 屏障

  • 不要用 *(volatile u32 *)addr = v; 直接访问 MMIO;统一使用内核提供的 readl/writel(或 ioread32/iowrite32)。

  • readl()/writel():大多数架构上带必要的 I/O 可见性屏障(比如在 writel() 前插入 wmb() 或在 readl() 后插入 rmb() 等),确保 MMIO 与普通内存的顺序。但它们的强度和位置是“架构相关”的。

  • readl_relaxed()/writel_relaxed()省略默认的 I/O 屏障,只做单次 MMIO 访问。需要你显式加 mb()/rmb()/wmb() 或专用 I/O 屏障来保证顺序。

  • mmiowb():在某些架构/场景下用来约束向不同设备的写入顺序(通常在解锁自旋锁后确保批量 MMIO 写顺序对设备可见)。

  • 经验法则:

    • 如果你对设备写 doorbell/控制寄存器,且之前更新了普通内存中的描述符/数据:wmb()(或 dma_wmb()),再 writel() doorbell

    • 如果要读取设备状态后再读普通内存里的缓冲区:readl() 取状态,再 rmb(),再读内存(或使用配套的 acquire 读方案)。

DMA 与缓存一致性(非一致平台尤需注意)

  • 可用 一致性 DMA(coherent)或 非一致性 DMA(streaming)。

  • 非一致性路径:

    • CPU → 设备(CPU 填好 buffer/描述符):

      • dma_sync_single_for_device(dev, dma_addr, len, DMA_TO_DEVICE)(或在某些轻量场景用 dma_wmb() 之后写 doorbell)

      • writel() doorbell/启动寄存器

    • 设备 → CPU(设备写回数据):

      • 中断/轮询得知完成

      • dma_sync_single_for_cpu(dev, dma_addr, len, DMA_FROM_DEVICE)

      • 再读 buffer(必要时配合 dma_rmb()/rmb()

  • 一致性 DMA路径(cache 一致):

    • 常用顺序:更新内存dma_wmb()writel();读回数据前 dma_rmb()

  • 简化口诀:“写前 wmb,读后 rmb;doorbell 前 wmb;拿状态后 rmb。”

常见并发模式示例

生产者(CPU0)/消费者(CPU1)单向通信(无锁队列一类)

/* 生产者:先写数据,再发布索引 */
buf[idx] = data;
smp_store_release(&tail, idx + 1);/* 消费者:先拿到已发布的索引,再读数据 */
int t = smp_load_acquire(&tail);
if (head < t) {item = buf[head];head++;
}
  • store_release 确保对 buf 的写在发布 tail 之前对其他 CPU 可见;

  • load_acquire 确保拿到新的 tail 后,再读 buf 一定能看到对应数据。

中断上下文与线程上下文

线程设置“已就绪标志”,中断处理里读取:

/* 线程上下文 */
WRITE_ONCE(flag, 1);
smp_wmb();          /* 或者把下一条换成 store_release */
writel(START, dev->doorbell);/* 中断上下文 */
status = readl(dev->status);
smp_rmb();          /* 或 load_acquire 来读取 flag 等 */
if (READ_ONCE(flag))handle();

更新 MMIO 描述符 + Doorbell

/* 更新环形队列的描述符(普通内存) */
desc->len = len;
desc->addr = dma_addr;
/* 确保描述符写入先于设备可见 */
dma_wmb();
/* 通知设备 */
writel(DBELL_KICK, dev->db_reg);
  1. 内存类型与顺序直觉

  • Normal(缓存able):ARM 默认弱内存模型,读写可能乱序,需要栅栏/acq-rel。

  • Device(nGnRnE / nGnRE / GRE):对同一设备 MMIO 访问有先后规则,但CPU 对普通内存与 MMIO 的相对顺序不一定天然符合你的期望;因此 Linux 以 readl/writel + 栅栏来提供统一模型。

  • 不要volatile 代替屏障;volatile 只约束编译器,不约束 CPU 重排序。

  1. 该用谁?最简决策表

目的用法
线程间发布数据smp_store_release / smp_load_acquire
强制完全排序smp_mb()
只约束读序/写序smp_rmb() / smp_wmb()
访问 MMIO(设备寄存器)readl()/writel()(性能敏感才考虑 _relaxed + 显式屏障)
Doorbell 前确保内存已可见dma_wmb() 或 wmb(),然后 writel()
设备写回后 CPU 读缓冲dma_sync_single_for_cpu() 或 dma_rmb()/rmb() 之后再读
上锁/解锁的顺序保证spin_lock=acquire,spin_unlock=release
  1. 常见坑

  • 只用 WRITE_ONCE/READ_ONCE没有配对的 acquire/release 或 smp_mb()——在 ARM 上会出错。

  • _relaxed 版本的 readl/writel 却忘记加 mb()/wmb()/rmb()

  • 在非一致性 DMA 平台忘了 dma_sync_single_for_*,导致读到脏缓存或设备看不到最新内存。

  • dsb sy 当成“万金油”滥用,性能大杀器;优先考虑 acquire/release 语义。

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

相关文章:

  • 商用厨房物联网智能化解决方案——打造环保、高效、安全的智慧餐饮新生态
  • C语言基础:(二十)自定义类型:结构体
  • 领码方案:通用物联网数据采集低代码集成平台——万物智联时代的黄金钥匙
  • 【Grafana】grafana-image-renderer配合python脚本实现仪表盘导出pdf
  • 车载软件架构 --- 赢得汽车软件开发竞赛
  • MySQL事务及原理详解
  • YAML格式笔记
  • SQL面试题及详细答案150道(41-60) --- 条件查询与分组篇
  • 【自记】Power BI 中 ALL、ALLSELECTED、ALLEXCEPT、ALLNOBLANKROW 的区别说明
  • 自学嵌入式第二十三天:数据结构(3)-双链表
  • SQL四大类命令(DQL、DML、DDL、DCL)
  • 第1课_Rust基础入门
  • Rust系统编程:从入门到实战的蜕变之旅
  • MySQL 数据与表结构导出 Excel 技术文档
  • 基础笔记8.20
  • Spring Cloud Gateway 负载均衡全面指南
  • 甘特图-项目可视化引擎|Highcharts.js 模块特征
  • Linux I/O 多路复用实战:Select/Poll 编程指南
  • Java主流框架全解析:从企业级开发到云原生
  • 通过自动化本地计算磁盘与块存储卷加密保护数据安全
  • 819 机器学习-决策树2
  • 学习threejs,打造宇宙星云背景
  • 芯科科技即将重磅亮相IOTE 2025深圳物联网展,以全面的无线技术及生态覆盖赋能万物智联
  • CentOS 系统 Java 开发测试环境搭建手册
  • CentOS 7.6安装崖山23.4.1.102企业版踩坑实战记录(单机)
  • Git 新手完全指南(二):在vscode中使用git
  • Linux 文本处理与 Shell 编程笔记:正则表达式、sed、awk 与变量脚本
  • CentOS 7/8 搭建 Samba 文件共享服务并与Windows无缝集成
  • centos配置ip地址不生效
  • 关于多个el-input的自动聚焦,每输入完一个el-input,自动聚焦到下一个