linux 内存屏障
Linux 内存屏障是用于控制 CPU 与编译器内存访问重排、保证多核 / 设备间可见性与顺序的底层同步原语,核心分为编译器屏障、CPU 硬件屏障、SMP 专用屏障、ACQUIRE/RELEASE 单向屏障及DMA/MMIO 专用屏障。
为什么需要内存屏障
- 编译器优化:会重排、合并、消除内存访问,破坏预期顺序。
- CPU 乱序执行:现代 CPU 为性能会乱序执行访存指令,多核间可见顺序可能与代码顺序不一致。
- 设备交互(MMIO/DMA):设备要求严格访问顺序,重排会导致硬件行为异常。
编译器屏障(仅影响编译器)
编译器屏障 = 只阻止【编译器】重排代码,不产生任何 CPU 指令,对 CPU 运行完全无影响,0 运行时开销。
- barrier():
__asm__ __volatile__("" ::: "memory"),阻止编译器跨屏障重排内存访问,不影响 CPU。 - READ_ONCE() / WRITE_ONCE():防止编译器对单变量的撕裂、合并、重排,保证单次访问的原子性与可见性假设 。
它到底解决什么问题?
编译器为了优化速度,会偷偷改变你的代码执行顺序,比如:
你写的代码:
a = 1; b = 2;编译器优化后可能变成:
b = 2; a = 1;只要单线程运行结果一样,编译器就敢乱排。
但在多核 / 无锁 / 驱动场景,这种重排会导致严重 bug!
编译器屏障就是用来禁止编译器做这种跨屏障的重排。
Linux 内核里的标准写法
barrier();它的本质是一段内嵌汇编:
#define barrier() __asm__ __volatile__("": : :"memory")作用:
- 告诉编译器:内存变了,你不知道发生了什么
- 编译器不能把屏障上方的内存访问重排到下方
- 编译器不能把屏障下方的内存访问重排到上方
- 编译器必须把寄存器里的缓存变量写回内存
- 不生成任何 CPU 指令
关键特性
1. 0 开销!
运行时不执行任何指令,不耗 CPU 周期。
2. 只管编译器,不管 CPU
- ✅ 阻止编译期重排
- ❌完全管不了 CPU 运行期的乱序执行
3. 强制 “内存同步”
- 屏障前,寄存器里的变量必须写回内存
- 屏障后,变量必须重新从内存读取
- 防止编译器把变量 “锁在寄存器里”
4. 是所有 SMP 屏障的基础
smp_rmb() smp_wmb() smp_mb()在单核 CPU上,全部退化为barrier()。
CPU 硬件屏障(影响 CPU + 编译器)
CPU 硬件屏障 = 强制 CPU 遵守内存访问顺序,禁止运行时乱序执行,会真实生成指令,有性能开销。
它和编译器屏障完全不是一个东西:
- 编译器屏障:管编译阶段
- CPU 硬件屏障:管CPU 实际执行阶段
现代 CPU 为了快,会:
- 乱序执行指令
- 把写操作先放进store buffer
- 把读操作放进load buffer
- 多核之间缓存同步有延迟
结果就是:代码顺序 ≠ CPU 执行顺序 ≠ 其他核看到的顺序
硬件屏障就是用来:让 CPU 停下来,把缓存 / 缓冲同步干净,再继续执行。
mb()—— 全内存屏障
- 作用:屏障之前所有的读、写必须全部全局可见之后才能执行屏障之后的任何读、写
- 对应指令:
- x86:
mfence - ARM64:
dsb ish/dmb ish
- x86:
- 开销:最大
rmb()—— 读屏障
- 只约束读 ↔ 读顺序
- 屏障前的所有读 → 先完成
- 屏障后的所有读 → 后执行
- 不限制写
- 开销:中等
wmb()—— 写屏障
- 只约束写 ↔ 写顺序
- 屏障前的所有写 → 先全局可见
- 屏障后的所有写 → 后开始
- 不限制读
- 开销:中等偏小
SMP 专用屏障(多核场景)
SMP 屏障 = 多核环境下才生效的 CPU 硬件屏障;单核环境下,自动退化成 0 开销的编译器屏障。
内核里就是这 4 个:
smp_mb() 全屏障 smp_rmb() 读屏障 smp_wmb() 写屏障 smp_read_barrier_depends() 数据依赖屏障内核里正常并发编程几乎都用这一组:
smp_mb()smp_rmb()smp_wmb()
规则:
- SMP 内核:等价于
mb() / rmb() / wmb() - UP 单核内核:自动退化为编译器屏障 barrier ()
目的:在不需要硬件屏障的场景自动省掉开销,提高性能。
为什么要设计 SMP 专用屏障?
因为:
- 单核(UP):不存在多核乱序,不需要 CPU 屏障,只要阻止编译器重排就行。
- 多核(SMP):必须用CPU 硬件屏障才能保证顺序。
Linux 内核希望一套代码兼容单核 + 多核,于是发明了smp_*()系列屏障。
#ifdef CONFIG_SMP #define smp_mb() mb() // 多核:用真正的硬件屏障 #else #define smp_mb() barrier() // 单核:只保留编译器屏障(0开销) #endif这就是 SMP 屏障的全部秘密!
SMP 屏障 vs 全局硬件屏障
| 屏障 | 作用 | 多核 SMP | 单核 UP | 适用场景 |
|---|---|---|---|---|
| smp_mb() | SMP 全屏障 | CPU 硬件屏障 | 编译器屏障 (0 开销) | 多核并发、无锁、内核通用 |
| mb() | 全局全屏障 | CPU 硬件屏障 | CPU 硬件屏障 | 驱动 MMIO、DMA、硬件交互 |
关键区别:
- smp_*():只为多核间同步服务
- mb()/rmb()/wmb():不管单核多核,强制硬件屏障,用于CPU 和硬件设备通信
- 内核并发、无锁、线程间同步 → 用 smp_*()
- 驱动、硬件寄存器、DMA → 用 mb ()/rmb ()/wmb ()
本质区别:
- smp_mb / smp_rmb / smp_wmb:只解决多核之间乱序,单核时不生成 CPU 指令
- mb / rmb / wmb:无论单核多核,都强制生成 CPU 硬件屏障指令
定义上的区别(内核宏)
#ifdef CONFIG_SMP #define smp_mb() mb() #define smp_rmb() rmb() #define smp_wmb() wmb() #else #define smp_mb() barrier() // 单核 = 纯编译器屏障 #define smp_rmb() barrier() #define smp_wmb() barrier() #endif- smp_是条件宏*
- mb/rmb/wmb 是真实硬件屏障
生效场景区别
smp_* 屏障
- 多核 SMP 系统→ 等价于硬件屏障
- 单核 UP 系统→只做编译器优化禁止,不产生任何 CPU 指令
- 目的:多核同步用,单核省性能
mb /rmb/wmb 硬件屏障
- 不管单核还是多核
- 永远生成硬件屏障指令(mfence、dmb ish 等)
- 目的:控制 CPU 与硬件(MMIO、DMA、设备)的时序
作用对象区别
smp_*
只保证:CPU 核 ↔ CPU 核之间的内存可见顺序
mb/rmb/wmb
保证:CPU 核 ↔ 其他 CPU 核CPU 核 ↔ 总线 / 桥 / 设备 / DMA全部顺序都要遵守
性能开销区别
- smp_mb:
- 多核:有硬件开销
- 单核:0 开销
- mb:
- 永远有硬件开销
使用场景分界线
用 smp_* 的场景(内核并发)
- 多核之间无锁通信
- 线程 / 进程之间共享数据
- 自旋锁、RCU、无锁队列
- 只要不碰硬件,都用 smp_*
用 mb/rmb/wmb 的场景(驱动 / 硬件)
- 操作 MMIO 寄存器
- DMA 缓冲区同步
- 写设备寄存器后必须保证顺序
- 任何CPU 和外设交互
什么时候必须用 smp_*()?
只要你写的是:
- 多核之间共享数据
- 无锁编程
- 内核同步逻辑(不是驱动)
一律用 smp_*(),不要用 mb ()!
经典例子:生产者 - 消费者(无锁)
// 生产者 CPU0 data = 100; smp_wmb(); // 多核:保证 data 先写完 ready = 1; // 消费者 CPU1 while (!ready); smp_rmb(); // 多核:保证后读 data print(data);什么时候绝对不能用 smp_*()?
操作硬件寄存器、MMIO、DMA 时!
比如:
// 错误!驱动里不能用 smp_wmb() writel(1, REG_ADDR); smp_wmb(); // 单核下会变成0开销,硬件时序出错! // 正确!驱动必须用全局硬件屏障 wmb() writel(1, REG_ADDR); wmb();原因:SMP 屏障只管 CPU 之间,不管 CPU 和硬件之间!
SMP 屏障的性能优势
- 多核:提供必要的硬件安全
- 单核:自动变成0 开销
这就是 Linux 高性能的秘密之一:不浪费任何一条指令!
不同架构开销差异
x86 /x86_64(强内存模型)
- 天然不会:写→写重排、读→读重排
- 所以:
smp_wmb()→ 通常只是编译器屏障smp_rmb()→ 通常只是编译器屏障- 只有
smp_mb()才是真实mfence→屏障开销很低
ARM64 / RISC-V / PowerPC(弱内存模型)
- CPU 允许大量重排
smp_rmb/smp_wmb/smp_mb都会生成真实的dmb / dsb指令→屏障开销明显更高
数据依赖屏障
这是Linux 内核里最轻量级的屏障,只适用于一种极其特殊的场景:CPU 读取指针 -> 通过指针读取数据 之间的顺序保证。
数据依赖屏障 = 专门保护 “指针读取 + 指针解引用” 的顺序
数据依赖屏障(Data Dependency Barrier)是最弱的读屏障,仅保证有数据依赖的读操作不被重排,比rmb轻量;
smp_read_barrier_depends():在多核SMP上等价于对应硬件屏障;在单核UP上退化为编译器屏障(barrier ()),兼顾性能与兼容性。
read_barrier_depends():数据依赖屏障,仅约束存在数据依赖的加载(如指针→数据)。
什么是数据依赖
数据依赖 = 后一次读的地址 / 值,完全由前一次读的结果决定。
- 典型模式:先读指针 → 再读指针指向的数据
- 例:
这里ptr = read_ptr(); // 读指针(Load 1) data = *ptr; // 读数据(Load 2,依赖 Load 1)Load 2的地址由Load 1决定,存在强数据依赖。
屏障语义(严格约束)
- 只约束:屏障前的读 → 屏障后有数据依赖的读,必须按序执行、按序可见。
- 不约束:
- 无依赖的读(如并行读两个独立变量)
- 写操作
- 读与写之间的顺序
- 隐含:编译器屏障(阻止编译器重排依赖读)
强度关系(从弱到强)
smp_read_barrier_depends() < smp_rmb() < smp_mb()
smp_read_barrier_depends:仅约束依赖读smp_rmb:约束所有读(隐含数据依赖屏障)smp_mb:约束所有读写
它到底解决什么问题?
正常逻辑(我们期望的顺序)
- 生产者先写好数据
- 生产者再把数据地址赋值给指针
- 消费者先读指针
- 消费者通过指针读数据 →必须读到最新数据
可能出问题的情况
弱内存模型架构(如早期 Alpha)CPU 可能先读数据,后读指针,导致读到旧数据。
这时候就必须用:
ptr = READ_ONCE(shared_ptr); smp_read_barrier_depends(); // 数据依赖屏障 val = *ptr;适用场景
条件 1:必须是指针依赖
必须是:
- 先读一个指针变量
- 再用这个指针去访问内存
不是指针依赖的场景,不能用。
条件 2:必须是读 -> 读顺序
只保护两个读操作之间的顺序。写操作不能用它。
条件 3:必须是无锁编程
你在做无锁队列、RCU、无锁链表,不能用锁 / 全屏障,追求极致性能。
内核接口
// 全局数据依赖屏障(极少用) read_barrier_depends() // SMP专用(内核主流) smp_read_barrier_depends()宏展开逻辑
// 内核定义(简化) #ifdef CONFIG_SMP // 多核:按架构实现依赖屏障(Alpha需指令,x86/ARM64多为空) #define smp_read_barrier_depends() __smp_read_barrier_depends() #else // 单核:完全空,0开销 #define smp_read_barrier_depends() do { } while (0) #endif架构实现差异
- DEC Alpha:必须生成硬件屏障指令(唯一强制架构)
- x86/x86_64:硬件天然保证依赖读顺序 →
smp_read_barrier_depends()为空 - ARM64:硬件保证依赖读顺序 → 为空
- 其他弱序架构:多数为空,仅编译器屏障
为什么需要它?(乱序风险)
1. 典型问题场景(生产者 - 消费者无锁)
// CPU1(生产者) data = 42; smp_wmb(); // 确保data先写完 ptr = &data; // 发布指针 // CPU2(消费者) while (!ptr); // 等待指针有效 // 风险:CPU2可能先读到ptr新值,却读到data旧值! val = *ptr; // 可能读到旧data(如0)原因:在 Alpha 等架构,指针可见 ≠ 指针指向的数据可见,无屏障会出现 “指针新、数据旧” 的违反依赖的乱序。
2. 正确用法(插入依赖屏障)
// CPU2(消费者) while (!ptr); smp_read_barrier_depends(); // 依赖屏障:ptr可见后,*ptr才可见 val = *ptr; // 保证读到最新data现代 CPU 上它的开销是多少?
x86 / ARM64 / RISC-V :数据依赖屏障 = 空指令,0 开销!这些架构硬件自动保证指针依赖顺序,不需要屏障指令。
只有古老的 Alpha CPU需要真正指令。
Linux 内核这么设计:
- 现代 CPU:0 开销
- 极端架构:自动提供保护
- 代码可移植
ACQUIRE/RELEASE 单向屏障(锁语义)
这是现代 Linux 内核最推荐、性能最好、锁底层都在用的屏障模型,比smp_mb()轻得多,而且语义更清晰。
核心定义
- ACQUIRE(获取):我读一个值,后面的内存访问不能跑到这次读前面
- RELEASE(释放):我写一个值,前面的内存访问不能跑到这次写后面
它们是单向屏障,只限制一半重排,所以比全屏障快很多。
Linux 内核对应接口
// 读 + ACQUIRE 语义 smp_load_acquire(p); // 写 + RELEASE 语义 smp_store_release(p, val);这两个是原子访问 + 屏障一体的接口,最常用。
- smp_load_acquire():ACQUIRE,保证其后所有访存不被重排到屏障前。
- smp_store_release():RELEASE,保证其前所有访存不被重排到屏障后。
- 配对使用实现临界区 “获取 - 释放” 语义,比全屏障更高效。
ACQUIRE 语义(smp_load_acquire)
x = smp_load_acquire(&a); // ↓ 下面所有读写 不能 重排到 ↑ 上面作用:
- 保证读 a 之后的所有内存访问
- 一定发生在读 a 完成之后
典型场景:拿到锁、拿到就绪标志、进入临界区
RELEASE 语义(smp_store_release)
// ↑ 前面所有读写 不能 重排到 ↓ 下面 smp_store_release(&a, x);作用:
- 保证写 a 之前的所有内存访问
- 一定先全局可见,再执行写 a
典型场景:释放锁、标记就绪、退出临界区
为什么叫 “单向屏障”?
- ACQUIRE:只限制后面 ← 往前跑
- RELEASE:只限制前面 → 往后跑
不像smp_mb()前后都卡死,所以开销小很多。
和 smp_mb () 的区别
| 屏障 | 约束方向 | 开销 | 架构指令 | 适用 |
|---|---|---|---|---|
| smp_mb() | 双向全序 | 高 | mfence / dsb ish | 强顺序场景 |
| ACQUIRE | 单向(后不超前) | 低 | ldapr / ldar + acquire | 读锁、取标志 |
| RELEASE | 单向(前不滞后) | 低 | stlr + release | 写锁、发标志 |
ACQUIRE/RELEASE ≈ 轻量版半屏障
经典配对:生产者 - 消费者
// 生产者 data = 100; smp_store_release(&ready, 1); // RELEASE:前面写完再发标志 // 消费者 while (!smp_load_acquire(&ready)); // ACQUIRE:拿到标志再读后面 print(data);这就是无锁编程最标准、最推荐写法。
和锁的关系
自旋锁、mutex 底层本质就是:
// 加锁 lock(); smp_load_acquire(...) // 解锁 smp_store_release(...) unlock();所以你用锁的时候,已经隐式用了 ACQUIRE/RELEASE。
架构差异
x86:acquire/release 几乎0 额外开销x86 强内存模型天然保证大部分顺序,只需要编译器屏障。
ARM64:
- acquire →
ldar/ldapr - release →
stlr比 full barrier(dsb)快非常多。
- acquire →
什么时候用 ACQUIRE/RELEASE?
只要满足:
- 先发布数据,再设标志→ 用RELEASE
- 先读标志,再读数据→ 用ACQUIRE
- 临界区开始 / 结束 → 用ACQUIRE/RELEASE
90% 的无锁场景,都不需要 smp_mb (),用这俩就够。
DMA/MMIO 专用屏障
两个场景
- MMIO:读写设备寄存器(
readl/writel) - DMA:CPU 写内存 → 设备去读;或设备写内存 → CPU 去读
这两种场景,CPU 乱序、桥片重排、写缓冲不刷新,都会直接硬件死机、数据错误。
dma_rmb()/dma_wmb()
DMA 缓冲区专用轻量屏障
dma_wmb():保证 CPU 写到 DMA 内存的顺序,对 DMA 设备可见dma_rmb():保证 CPU 从 DMA 内存读的顺序,和设备写入一致
特点:
- 比
rmb/wmb轻 - 只保证设备可见性,不保证多核之间可见
- 驱动操作 DMA 缓冲区标配
mmiowb()
MMIO 写屏障(MMIO Write Barrier)
专门保证:CPU 对设备寄存器的多次写操作,按代码顺序到达设备
很多 ARM/PCIe 桥片会重排 MMIO 写,不加mmiowb()会导致:
- 设备配置顺序错乱
- 驱动无法初始化
- 硬件异常
全局硬件屏障
mb()全屏障rmb()读屏障wmb()写屏障
在驱动里,这三个是最强保证:
- 禁止 CPU 重排
- 强制写缓冲排空
- 对其他 CPU、对设备、对总线都强序
为什么不能用 smp_*()?
因为:
smp_wmb()在单核内核下会变成纯编译器屏障,没有硬件指令- 但设备不管你是不是单核,必须强制顺序
所以:驱动 = 禁止使用 smp_rmb /smp_wmb/smp_mb
典型使用场景
场景 1:写 MMIO 寄存器
writel(REG1_VAL, addr1); mmiowb(); // 保证先写 addr1,再写 addr2 writel(REG2_VAL, addr2);场景 2:DMA 缓冲区准备
/* CPU 填充 DMA 数据 */ buf[0] = 1; buf[1] = 2; dma_wmb(); // 保证数据写完,再通知硬件 /* 通知 DMA 引擎启动 */ writel(1, DMA_START_REG);场景 3:读完 DMA 数据
/* 等待 DMA 完成 */ while (!(readl(DMA_STATUS_REG) & DMA_DONE)); dma_rmb(); // 保证先等完成,再读数据 /* 读取数据 */ val = buf[0];整体总览(从弱到强)
编译器屏障 barrier() ↓ 数据依赖屏障 smp_read_barrier_depends() ↓ ACQUIRE / RELEASE 单向屏障 ↓ DMA 专用屏障 dma_rmb / dma_wmb ↓ 读/写屏障 smp_rmb / smp_wmb ↓ 全屏障 smp_mb ↓ 全局硬件屏障 rmb / wmb / mb(驱动/MMIO/DMA)强度越强 → 约束越多 → 开销越大
一张表终极总结
| 屏障 | 强度 | 开销 | 主要约束 | 适用 |
|---|---|---|---|---|
| barrier() | 1 | 0 | 编译器 | 单核、优化隔离 |
| smp_read_barrier_depends() | 2 | ≈0 | 数据依赖读 | RCU、无锁指针 |
| ACQUIRE/RELEASE | 3 | 低 | 单向临界区 | 锁、无锁队列 |
| dma_rmb/dma_wmb | 4 | 中 | DMA 缓冲区 | 驱动 DMA |
| smp_rmb/smp_wmb | 5 | 较高 | 全读 / 全写有序 | 多核同步 |
| smp_mb | 6 | 高 | 全读写屏障 | 强有序场景 |
| mb/rmb/wmb | 7 | 最高 | CPU + 设备全序 | 驱动 MMIO/DMA |
屏障语义与配对规则
- 写屏障 ↔ 读屏障:生产者写数据→smp_wmb ()→写标志;消费者读标志→smp_rmb ()→读数据 。
- 数据依赖屏障:读指针→smp_read_barrier_depends ()→读指针指向数据(Alpha 等宽松架构必需) 。
- ACQUIRE ↔ RELEASE:锁的底层实现,临界区前 ACQUIRE、后 RELEASE。
- 全屏障:读写混合强序场景,保证传递性 。
架构实现差异(底层指令)
- x86:
mfence(mb)、lfence(rmb)、sfence(wmb)。 - ARM64:
dsb ish(mb)、dmb ishld(rmb)、dmb ishst(wmb)。 - RISC-V:
fence指令组合。
隐式屏障(常用原语自带)
- 自旋锁、互斥锁、信号量:隐含 ACQUIRE/RELEASE。
- 原子操作(xchg、cmpxchg、atomic_*_return):隐含 SMP 全屏障。
- schedule ()、wake_up ()、set_current_state ():隐含全屏障。
使用原则
- 优先 SMP 屏障:多核用
smp_*,UP 自动退化。 - 设备交互用全局屏障:MMIO/DMA 必须用
mb/rmb/wmb而非smp_*。 - 优先高级原语:锁、原子、RCU 已内置屏障,尽量不手动加屏障。
- 配对使用:单侧屏障通常无效,需对端配合。
典型场景示例(生产者 - 消费者)
// 生产者 data = 100; smp_wmb(); // 确保data先写完 ready = 1; // 消费者 while (!ready); smp_rmb(); // 确保读ready后再读data print(data);内存屏障会带来性能开销
会,而且开销不小,只是不同屏障、不同架构、不同场景下,开销差异极大。
简单结论先说:
- 内存屏障一定会拖慢 CPU 执行效率
- 越 “强” 的屏障,代价越大
- ARM64 / RISC-V 这类弱序架构,屏障开销明显高于 x86
- 多核高并发下,屏障会成为热点瓶颈
内存屏障本质是强制 CPU 放弃乱序优化,让流水线 “等一等”。
开销主要来自三件事:
清空 / 等待存储队列、加载队列CPU 内部有大量缓存的写缓冲、读缓冲、失效队列,屏障要等它们排空、同步完成才能继续。
打断流水线,阻塞后续指令屏障是 “串行点”,后面指令不能提前执行,流水线被堵。
触发缓存一致性同步(多核)SMP 屏障会触发 CPU 间缓存同步(MESI 协议),跨核通信本身就慢。
不同屏障的开销大小(从低到高)
1. 编译器屏障barrier()
几乎无开销
- 只影响编译,不产生任何指令
- 运行时代价 ≈ 0
2. 数据依赖屏障smp_read_barrier_depends()
开销极低
- 大部分架构(x86/ARM64)直接是空操作
- 只有 Alpha 这种极端架构才需要
3. ACQUIRE / RELEASE 屏障
smp_load_acquire/smp_store_release轻量级开销
- 只限制单向重排,不强制全缓冲排空
- 比 full barrier 快很多
- 现代锁(mutex、spinlock)底层基本都用它
4. 读 / 写屏障rmb()/wmb()/smp_rmb()/smp_wmb()
中等开销
- 只约束读或只约束写
- 比全屏障轻,但仍然会阻塞流水线
5. 全内存屏障mb()/smp_mb()
开销最大
- 强制所有读写按代码顺序全局可见
- 排空所有写缓冲、等待读完成、同步缓存
- 多核高并发下,连续
smp_mb()性能会雪崩式下降
架构差异巨大(重点)
x86 / x86_64
- 本身是强内存模型
- 大部分读 / 写重排天然不会发生
smp_rmb()/smp_wmb()很多时候只是编译器屏障- 只有
mfence这类全屏障才有真实开销
ARM64 / RISC-V / PowerPC
弱内存模型,屏障开销非常明显
- 几乎所有
smp_*屏障都会生成真实指令:dmb ishdsb ish
- 频繁使用会显著影响吞吐量
Linux 内核为什么尽量少用全屏障?
内核开发者有个共识:
能不用 mb () 就不用,能用 smp_load_acquire/release 就不用 smp_mb ()
原因:
- 全屏障会序列化整个 CPU 流水线
- 多核下会引发缓存一致性风暴
- 高频路径(网络、调度、RCU)一旦滥用屏障,系统整体性能直接拉胯
什么时候开销可以忽略?
- 屏障不在热点循环里
- 每秒执行次数很少(比如初始化、设备配置、慢速路径)
- 单核 UP 环境下,
smp_*屏障退化成编译器屏障,几乎无开销
