[SPDK/NVMe存储技术分析]006 - 内存屏障(MB)
在多核(SMP)多线程的情况下,如果不知道CPU乱序执行的话,将会是一场噩梦,因为无论怎么进行代码Review也不可能发现跟内存屏障(MB)相关的Bug。内存屏障分为两类:
- 跟编译有关的内存屏障: 告诉编译器,不要优化我,俺不需要
- 跟CPU有关的内存屏障: 告诉CPU, 不要乱序执行,谢谢
1. NVMeDirect中的内存屏障
/* nvmedirect/include/lib_nvmed.h */
38 #define COMPILER_BARRIER() asm volatile("" ::: "memory")
由于NVMeDirect依赖于Linux内核的NVMe驱动(nvme.ko)实现,所以NVMeDirect并不需要实现它自己的与CPU相关的内存屏障。
2. SPDK中的内存屏障
/* src/spdk-17.07.1/include/spdk/barrier.h */ 47 /** Compiler memory barrier */
48 #define spdk_compiler_barrier() __asm volatile("" ::: "memory")
49
50 /** Write memory barrier */
51 #define spdk_wmb() __asm volatile("sfence" ::: "memory")
52
53 /** Full read/write memory barrier */
54 #define spdk_mb() __asm volatile("mfence" ::: "memory")
在SPDK中,不仅实现了与编译相关的内存屏障,还实现了与CPU有关的内存屏障。 但是, 在与CPU有关的MB中, 读内存屏障(Read memory barrier)并没有实现。
3. DPDK中的内存屏障
在DPDK中,内存屏障的实现要复杂一点,因为支持x86, ARM和PowerPC三种平台。 以x86为例,代码实现如下:
- 与编译相关的MB
/* src/dpdk-17.08/lib/librte_eal/common/include/generic/rte_atomic.h */ 132 /**
133 * Compiler barrier.
134 *
135 * Guarantees that operation reordering does not occur at compile time
136 * for operations directly before and after the barrier.
137 */
138 #define rte_compiler_barrier() do { \
139 asm volatile ("" : : : "memory"); \
140 } while(0)
- 与CPU相关的MB
/* src/dpdk-17.08/lib/librte_eal/common/include/arch/x86/rte_atomic.h */ 52 #define rte_mb() _mm_mfence()
54 #define rte_wmb() _mm_sfence()
56 #define rte_rmb() _mm_lfence() 58 #define rte_smp_mb() rte_mb()
60 #define rte_smp_wmb() rte_compiler_barrier()
62 #define rte_smp_rmb() rte_compiler_barrier() 64 #define rte_io_mb() rte_mb()
66 #define rte_io_wmb() rte_compiler_barrier()
68 #define rte_io_rmb() rte_compiler_barrier()
另外,DPDK在对ARM32的MB支持中,使用了gcc的内嵌函数__sync_synchronize(), 例如:
/* src/dpdk-17.08/lib/librte_eal/common/include/arch/arm/rte_atomic_32.h */ 52 #define rte_mb() __sync_synchronize()
60 #define rte_wmb() do { asm volatile ("dmb st" : : : "memory"); } while (0)
68 #define rte_rmb() __sync_synchronize()
于是,让我们反汇编看看gcc的__sync_synchronize()到底是怎么回事。
$ cat -n foo.c
1 int main(int argc, char *argv[])
2 {
3 int n = 0x1;
4 __sync_synchronize();
5 return ++n;
6 }
$ gcc -g -Wall -m32 -o foo foo.c
$ gdb foo
...<snip>...
(gdb) disas /m main
Dump of assembler code for function main:
2 {
0x080483ed <+0>: push %ebp
0x080483ee <+1>: mov %esp,%ebp
0x080483f0 <+3>: sub $0x10,%esp 3 int n = 0x1;
0x080483f3 <+6>: movl $0x1,-0x4(%ebp) 4 __sync_synchronize();
0x080483fa <+13>: lock orl $0x0,(%esp) 5 return ++n;
0x080483ff <+18>: addl $0x1,-0x4(%ebp)
0x08048403 <+22>: mov -0x4(%ebp),%eax 6 }
0x08048406 <+25>: leave
0x08048407 <+26>: ret End of assembler dump. $ gcc -g -Wall -m64 -o foo foo.c
$ gdb foo
...<snip>...
(gdb) disas /m main
Dump of assembler code for function main:
2 {
0x00000000004004d6 <+0>: push %rbp
0x00000000004004d7 <+1>: mov %rsp,%rbp
0x00000000004004da <+4>: mov %edi,-0x14(%rbp)
0x00000000004004dd <+7>: mov %rsi,-0x20(%rbp) 3 int n = 0x1;
0x00000000004004e1 <+11>: movl $0x1,-0x4(%rbp) 4 __sync_synchronize();
0x00000000004004e8 <+18>: mfence 5 return ++n;
0x00000000004004eb <+21>: addl $0x1,-0x4(%rbp)
0x00000000004004ef <+25>: mov -0x4(%rbp),%eax 6 }
0x00000000004004f2 <+28>: pop %rbp
0x00000000004004f3 <+29>: retq End of assembler dump.
因为没有ARM平台,就在x86上分别进行32位和64位的编译,于是发现__sync_synchronize()对应的汇编指令是
- 32位
4 __sync_synchronize();
0x080483fa <+13>: lock orl $0x0,(%esp)
- 64位
4 __sync_synchronize();
0x00000000004004e8 <+18>: mfence
关于lock指令前缀和mfence指令,后面再讲。
4. Linux内核中的内存屏障
Linux内核支持很多种平台,这里仅以x86为例:
/* linux-4.11.3/arch/x86/include/asm/barrier.h */ 13 #ifdef CONFIG_X86_32
14 #define mb() asm volatile(ALTERNATIVE("lock; addl $0,0(%%esp)", "mfence", \
15 X86_FEATURE_XMM2) ::: "memory", "cc")
16 #define rmb() asm volatile(ALTERNATIVE("lock; addl $0,0(%%esp)", "lfence", \
17 X86_FEATURE_XMM2) ::: "memory", "cc")
18 #define wmb() asm volatile(ALTERNATIVE("lock; addl $0,0(%%esp)", "sfence", \
19 X86_FEATURE_XMM2) ::: "memory", "cc")
20 #else
21 #define mb() asm volatile("mfence" ::: "memory")
22 #define rmb() asm volatile("lfence" ::: "memory")
23 #define wmb() asm volatile("sfence" ::: "memory")
24 #endif
5. 总结
5.1 在x86_64平台上实现内存屏障(MB)
从NVMeDirect到SPDK, 再到DPDK和Linux内核, 我们可以得出在x86_64平台上,与内存屏障(MB)有关的实现可归纳为:
- 与编译有关的MB实现
#define XXX_compiler_barrier() asm volatile("" ::: "memory")
- 与CPU有关的MB实现
#define XXX_mb asm volatile("mfence" ::: "memory")
#define XXX_rmb asm volatile("lfence" ::: "memory")
#define XXX_wmb asm volatile("sfence" ::: "memory")
其中,
- volatile是C语言的关键字,主要目的是告诉编译器不要做优化。 关于volatile的说明, 请参考这里。
- mfence是汇编指令,用于设定读写屏障(Memory)。有关mfence指令,请参考这里。
- lfence是汇编指令,用于设定读屏障 (Load)。
- sfence也是汇编指令, 用于设定写屏障 (Store)。
5.2 lock指令前缀
lock指令前缀与原子操作有关。对于Lock指令前缀的总线锁,早期CPU芯片上有一条引线#HLOCK pin, 如果汇编语言的程序中在一条指令前面加上前缀"lock"(表示锁总线),经过汇编以后的机器码就使CPU在执行这条指令的时候把#HLOCK pin的电平拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上的别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多CPU环境中的原子性。
5.3 使用CPU内存屏障的根本原因
在SMP(对称多处理器)中,CPU是多核的,每个核有自己的cache,读写内存都先通过cache。然而内存只有一个,核有多个,也就是说,同一份数据在内存中只有一份,但却可能同时存在于多个cache line中。那么,如何进行同步? 答案就是原子操作,注意原子操作的前提是独占。假如一个变量X同时存在于核1和核2的cache line中,那么当核1想要对X进行"原子加(atomic add)"的时候必须先独占这个变量X,也就是告诉核2变量X的值在你的cache line已经失效了,以后想要操作X的时候到哥哥我这里来取最新的值。这看起来非常像锁,但是没有用到锁。(P.S.: 无锁队列的实现其实都离不开原子操作) 因此,我们可以这么认为,内存屏障(mb, wmb, rmb)的本质是用来在CPU各个核的cache line中进行通信,保证内存数据的更新具有原子性。
扩展阅读:
- Paper: Memory Barriers: a Hardware View for Software Hackers
- Paper: Mathematizing C++ Concurrency
- Wikipedia: https://en.wikipedia.org/wiki/Memory_barrier
- Blog: 巧夺天工的kfifo(修订版)
- Blog: Linux 2.6内核中新的锁机制--RCU
People seldom do what they believe in. They do what is convenient, then repent. | 人们很少做他们相信是对的事。他们做比较方便做的事,然后便会后悔。
[SPDK/NVMe存储技术分析]006 - 内存屏障(MB)的更多相关文章
- [SPDK/NVMe存储技术分析]008 - RDMA概述
毫无疑问地,用来取代iSCSI/iSER(iSCSI Extensions for RDMA)技术的NVMe over Fabrics着实让RDMA又火了一把.在介绍NVMe over Fabrics ...
- [SPDK/NVMe存储技术分析]004 - SSD设备的发现
源代码及NVMe协议版本 SPDK : spdk-17.07.1 DPDK : dpdk-17.08 NVMe Spec: 1.2.1 基本分析方法 01 - 到官网http://www.spdk.i ...
- [SPDK/NVMe存储技术分析]003 - NVMeDirect论文
说明: 之所以要翻译这篇论文,是因为参考此论文可以很好地理解SPDK/NVMe的设计思想. NVMeDirect: A User-space I/O Framework for Application ...
- [SPDK/NVMe存储技术分析]002 - SPDK官方介绍
Introduction to the Storage Performance Development Kit (SPDK) | SPDK概述 By Jonathan S. (Intel), Upda ...
- [SPDK/NVMe存储技术分析]001 - SPDK/NVMe概述
1. NVMe概述 NVMe是一个针对基于PCIe的固态硬盘的高性能的.可扩展的主机控制器接口. NVMe的显著特征是提供多个队列来处理I/O命令.单个NVMe设备支持多达64K个I/O 队列,每个I ...
- [SPDK/NVMe存储技术分析]015 - 理解内存注册(Memory Registration)
使用RDMA, 必然关系到内存区域(Memory Region)的注册问题.在本文中,我们将以mlx5 HCA卡为例回答如下几个问题: 为什么需要注册内存区域? 注册内存区域有嘛好处? 注册内存区域的 ...
- [SPDK/NVMe存储技术分析]005 - DPDK概述
注: 之所以要中英文对照翻译下面的文章,是因为SPDK严重依赖于DPDK的实现. Introduction to DPDK: Architecture and PrinciplesDPDK概论:体系结 ...
- [SPDK/NVMe存储技术分析]012 - 用户态ibv_post_send()源码分析
OFA定义了一组标准的Verbs,并提供了一个标准库libibvers.在用户态实现NVMe over RDMA的Host(i.e. Initiator)和Target, 少不了要跟OFA定义的Ver ...
- [SPDK/NVMe存储技术分析]014 - (NVMe over PCIe)Host端的命令处理流程
NVMe over PCIe最新的NVMe协议是1.3. 在7.2.1讲了Command Processing流程.有图有真相. This section describes command subm ...
随机推荐
- 教你用Elastic Search:运行第一条Hello World搜索命令
摘要:Elastic Search可实时对数据库进行全文检索.处理同义词.从同样的数据中生成分析和聚合数据. 本文分享自华为云社区<Elastic Search入门(一): 简介,安装,运行第一 ...
- 4、前端--浮动、定位、是否脱离文档流、溢出属性、z-index、透明度、JavaScript简介
浮动 # ps:html代码时没有缩进一说的 全部写在一行也可以 """浮动主要就是用于页面布局的!!!""" # 浮动带来的负面影响 &q ...
- LibOpenCM3(三) .ld文件(连接器脚本)和startup代码说明
目录 LibOpenCM3(一) Linux下命令行开发环境配置 LibOpenCM3(二) 项目模板 Makefile分析 LibOpenCM3(三) .ld文件(连接器脚本)和startup代码说 ...
- 力扣算法经典第一题——两数之和(Java两种方式实现)
一.题目 难度:简单 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数, 并返回它们的数组下标. 你可以假设每种输入只会对应一 ...
- node打标签、污点
标签 1.查看当前节点的标签 kubectl get node --show-labels 2.给节点打标签 kubectl label node nodename key=value. 3.删除标签 ...
- Spring Cloud Gateway现高风险漏洞,建议采取措施加强防护
大家好,我是DD 3月1日,Spring官方博客发布了一篇关于Spring Cloud Gateway的CVE报告. 其中包含一个高风险漏洞和一个中风险漏洞,建议有使用Spring Cloud Gat ...
- 深度学习分类问题中accuracy等评价指标的理解
在处理深度学习分类问题时,会用到一些评价指标,如accuracy(准确率)等.刚开始接触时会感觉有点多有点绕,不太好理解.本文写出我的理解,同时以语音唤醒(唤醒词识别)来举例,希望能加深理解这些指标. ...
- 案例三:shell统计ip访问情况并分析访问日志
题目要求 有日志 1.log,部分内容如下: 112.111.12.248 – [25/Sep/2013:16:08:31 +0800]formula-x.haotui.com"/secco ...
- [炼丹术]基于SwinTransformer的目标检测训练模型学习总结
基于SwinTransformer的目标检测训练模型学习总结 一.简要介绍 Swin Transformer是2021年提出的,是一种基于Transformer的一种深度学习网络结构,在目标检测.实例 ...
- AcWing 325. 计算机
传送门 题目大意: 一棵无根树,每条边有一个距离,求每个顶点到距离其最远的顶点的距离. 思路: 考虑树形DP+换根. 令D[x]x到以x为根的子树当中的最长距离,d[x]为次长距离,U[x]为x向上走 ...