NVMeOF笔记

1. 基本概念

1.1 Capsule

NVMeoF使用 Capsule 来进行数据传输,有两种 Capsule ,1) command capsule,2)response capsule。

command capsule包含一个SQE(它是对NVMe over PCIe中SQE的修改和扩展)和一个可选的数据或SGL部分。

command capsule由主机下发给NVM SubSystem,最少要包含一个SQE,也就是64Byte。SQE中包含主机下发给NVM SubSystem的命令,NVMeoF中有三类命令:(1)Admin command, (2)I/O command, (3)Fabrics command。command capsule的结构如下图:

NVMeOF笔记_第1张图片

response capsule包含一个CQE(它是对NVMe over PCIe中CQE的修改和扩展)和一个可选的数据部分。

response capsule由NVM SubSystem回复给主机,最少要包含一个CQE,也就是16Byte。CQE中包含某一条命令的完成结果。response capsule的结构如下图:

NVMeOF笔记_第2张图片

NVMe over PCIe可以使用PRP或者SGL来进行用户数据的传输。NVMe over Fabric要求所有命令只能使用SGL来传输用户数据。SGL可以指定数据在capsule中的位置,或者使用NVMe传输特定数据传输机制传输数据所需的信息(例如RDMA)。

1.2 Command Set

NVMeoF 除了包含 NVMe 协议的 Admin CommandIO Command 命令集外,还包含它特有的 Fabrics CommandFabrics Command 可以用来建立连接,认证,获取或设置NVM子系统中控制器的属性。所有Fabrics命令都可以被提交到Admin队列,有些Fabrics命令也可以提交到I/O队列。

NVMeOF笔记_第3张图片

1.3 属性Properties

Properties其实就是Controller内部维护的一些寄存器值或者内存值,用于表示自身的一些信息。主机可以通过Property Get命令获取property的值,可以用Property Set命令修改property值,Property Get命令和Property Set命令都归属Fabric命令集。Properties只能通过Admin队列来访问。

1.4 Discovery

NVMe over Fabric定义了一个Discovery机制,用于主机确定主机可以访问的NVM subsystem。一个Discovery控制器只提供Discovery Log Page,它不实现IO队列,也不暴露namespace。

Discovery Service是一个实现了Discovery Controller的NVM Subsystem。

Discovery Log Page中的每一个条目要么存放着一个NVM Subsystem的信息,这样主机可以通过这些必要的信息来访问这个NVM Subsystem,要么是另一个Discovery Service的信息。

2. 控制器结构

2.1 Identify Controller Data Structure

在主机内存中有专门的一块数据结构来描述SSD Controller的一些属性,叫做Identify Controller Data Structure 。可以使用 nvme id-ctrl /dev/nvme0 来查看设备/dev/nvme0的属性。

NVMeoF扩展了这个数据结构,添加了一些NVMeoF特有的属性。总结如下:

  • I/O Queue Command Capsule Supported Size (IOCCSZ):一条IO命令的最大大小,单位是16byte,这个值最小应该是4,表示一条IO命令最大应该是4 * 16 = 64bytes。
  • I/O Queue Response Capsule Supported Size (IORCSZ):一条IO响应的最大大小,单位是16byte,这个值最小应该是1,表示一条IO响应最大应该是1 * 16 = 16bytes。
  • In Capsule Data Offset (ICDOFF)

2.2 控制器模型

NVMeoF 规定了两种控制器模型,(1)动态模型,(2)静态模型。一个NVM子系统要么使用动态模型,要么使用静态模型。

在动态模型中,NVM子系统会按需分配控制器给请求建立连接的Host。分配给同一个Host的控制器们具有相同的状态(比如,控制器所拥有的命名空间,控制器的一些属性),当Host改变某一个动态控制器时(比如给这个控制器添加一个命名空间),不会影响其他的动态控制器。Host 通过在 Fabrics Connect command 中指定Controller ID 为 FFFFh,来使用动态控制器模型。

在静态模型中,被分配给同一个Host的控制器们可能具有不同的状态

2.3 NVMeoF队列初始化

首先Host会与NVM子系统之间建立传输层的连接,在NVMe over ROCE上就是通过RDMACM建立RDMA连接。

然后Host会向NVM子系统发送 Fabrics Connect Command 来与NVM子系统的一个控制器建立连接,并且在Host端和Controller上都建立Admin Queue。

最后,Host会配置与其关联的Controller,然后创建IO Queue。

下图展示了这一过程:

在Host与Controller之间使用 Fabrics Connect Command 命令建立关联之后,Controller会进行一些初始化工作。

  1. NVMe带内认证(in-band authentication),如果需要的话。

  2. Host使用 Property Get 命令获取Controller的一些属性。

  3. Host使用 Property Set 命令设置Controller的一些属性,包括:

    • Controller所使用的仲裁机制

    • 告诉Controller Host端的物理页大小

  4. Controller设置CC.EN为1,来使能自己。

  5. Host等待Controller转到ready状态,只有转到ready状态,Controller才能处理Host端发过来的命令。Controller设置CSTS.RDY为1表示自己进入了ready状态。

  6. Host发送 Identify command 来获取Controller的数据结构Identify Controller Data Structure。

  7. Host发送 Identify command 来获取命名空间的数据结构Identify Namespace Data Structure。

  8. Host使用 Connect Command 创建IO队列对。

  9. 要启用可选事件的异步通知,主机应发出 Set Features 命令指定要启用的事件。

抓包来看一下建立连接的过程:

  1. 主机使用RDMACM请求与NVM子系统建立连接。这时候Host端在自己这里建立了一个RDMAQueue Pair,QP Number = 11,Controller侧也建立了一个RDMA Queue Pair,QP Number = 15,这两个QP Pair之间建立了可靠的RC连接。这时NVMeoF将RDMA作为自己的传输层,传输层的连接建立完毕,后面NVMeoF的IO Queue和Command Queue就可以共享使用这个传输层连接来进行通信(NVMe over RDMA不允许多个NVMeoF的队列队共享同一个RDMA队列对),当然,也可以为NVMeoF的每一个队列对单独建立一个RDMA传输层连接。

202205141739203.png

  1. 主机使用 Fabrics Connect Command 命令请求与Controller的Admin Queue建立连接。

202205141740459.png

看一下 Fabrics Connect Command 命令里面SQE的结构。Queue ID为0,说明想要和Controller的Admin Queue建立连接,同时这个SQE里面携带了一个SGL,告诉Controller此次连接需要的数据所在的位置,同时给了一个Key,因为**随后Controller会发送RDMA Read命令读取Host的这块内存,获取此次连接所需要的数据。**而RDMA Read想要读取Host的内存,是需要Key的。

连接建立完毕后,Controller会向Host发送一个CQE。

  1. Host使用 Property GetProperty Set 命令获取并设置Controller的属性。

202205141740255.png

  1. Host使用询问Controller的状态是否为Ready状态。

image-20220514155105657

  1. 随后就是一些初始化的相关交互。省略。
  2. 后面,Host与Controller端建立了四个IO Queue。

NVMeOF笔记_第4张图片

3. NVMe over RDMA

3.1 Capsules and SGLs

对于Fabrics commands ,它既可以被提交到 Admin Queue,也可以被提交到 IO Queue。不管怎么样,Fabrics commands 的大小是固定的,command capsule 是64bytes,response capsule 是16bytes。

对于Admin commandsAdmin Queue 中的capsule大小也是固定的(Admin Queue中的capsule只包含SQE或者CQE,不会包含有额外的数据),同样的,command capsule 是64bytes,response capsule 是16bytes。

对于 IO commandsCommand capsules的大小是 IOCCSZ * 16 bytes,IOCCSZ 是Controller的一个属性。Command capsules的大小是固定的16bytes。

总结如下表:

image-20220514165528243

3.2 队列映射

NVMeoF的IO Queue会被映射到RDMA的SQ和RQ上,并且是一对一的映射,即不能将多个NVMeoF的IO Queue映射到同一个RDMA的SQ和RQ。

3.3 数据传输

NVMeoF的 Command CapsulesResponse Capsules 的传输使用的是RDMA的双边原语Send、Receive,用户数据的传输使用的是单边原语Read、Write。

所有的Read、Write都是由Controller发起的。当然,在使用Send、原语传输IO Command的时候,也可以直接在Capsule里面包含用户数据。

下表总结了NVMe over RDMA的数据交互的顺序:

2. 配置NVMe over ROCE环境

使用 femusoft-roce 来搭建实验环境。

femu 是基于qemu 的SSD模拟器,使用femu 可以创建带有虚拟SSD的虚拟机。我们创建两个虚拟机,一个虚拟机用作host端,一个用作target端。在虚拟机上安装 soft-roce 搭建一个虚拟的RDMA环境,然后配置ROCE作为NVMeoF的传输层。

2.1 搭建femu环境

femu 是一个SSD模拟器,GitHub地址:https://github.com/ucare-uchicago/FEMU

  1. 安装所需依赖
git clone https://github.com/ucare-uchicago/femu.git
cd femu
mkdir build-femu
# Switch to the FEMU building directory
cd build-femu
# Copy femu script
cp ../femu-scripts/femu-copy-scripts.sh .
./femu-copy-scripts.sh .
# only Debian/Ubuntu based distributions supported
sudo ./pkgdep.sh
  1. 编译并安装
./femu-compile.sh

编译完成后,在 build-femu 文件夹下能看到四个脚本文件,1)run-blackbox.sh,2)run-nossd.sh,3)run-whitebox.sh,4)run-zns.sh,分别用来启动所支持的四种类型的SSD模拟器。

  1. 下载虚拟机镜像文件

新建 images 文件夹,下载并解压 femu 所提供的官方虚拟机镜像。

mkdir -p ~/images
cd ~/images
wget http://people.cs.uchicago.edu/~huaicheng/femu/femu-vm.tar.xz
tar xJvf femu-vm.tar.xz

解压完成后会得到两个文件:1)u20s.qcow2,2)u20s.md5sum。

检查虚拟机镜像的完整性:

md5sum u20s.qcow2 > tmp.md5sum
diff tmp.md5sum u20s.md5sum

这时就得到了一个已经配置好的虚拟机镜像文件,叫u20s.qcow2。**虚拟机的用户名和密码都是femu。**后面就可以使用 build-femu 文件夹下的四个启动脚本来启动一个虚拟SSD。这些启动脚本会去家目录下的images文件夹下寻找名叫 u20s.qcow2 的虚拟机镜像文件,所以你需要确保在这个文件夹下有这么个镜像文件,当然,你也可以修改启动脚本,自定义镜像文件的位置。

  1. 在宿主机上创建网桥

如果使用 femu 官方的启动脚本启动虚拟机,会发现虚拟机无法ping通宿主机,多个虚拟机之间也无法ping通,我们需要在宿主机上创建一个网桥来解决这个问题。

sudo apt install bridge-utils #安装必要工具
sudo apt install uml-utilities

sudo ifconfig "eno1" down    				# 关闭网卡eno1
sudo brctl addbr br0					# 添加网桥br0
sudo brctl addif br0 "eno1"				# 将eno1网口的配置复制一份到br0
sudo brctl stp br0 off					# 关闭网桥
sudo brctl setfd br0 1					# 设置网桥的fd(标识符)
sudo brctl sethello br0 1				# 设置hello协议的时延
sudo ifconfig br0 0.0.0.0 promisc up	# 开启网桥br0
sudo ifconfig "eno1" 0.0.0.0 promisc up	# 开启eno1网卡
sudo dhclient br0						# 向路由器获取IP地址
sudo brctl show br0						# 打印网桥信息
sudo brctl showstp br0					# 打印网桥stp信息
sudo tunctl -t tap0 -u root				# 新建一个tap0网卡,供host虚拟机使用
sudo tunctl -t tap1 -u root				# 新建一个tap1网卡,供target虚拟机使用
sudo brctl addif br0 tap0				# 将tap0网卡加入到br0上
sudo brctl addif br0 tap1				# 将tap1网卡加入到br0上
sudo ifconfig tap0 0.0.0.0 promisc up	# 开启tap0网卡
sudo ifconfig tap1 0.0.0.0 promisc up	# 开启tap1网卡
brctl showstp br0						# 打印网桥stp信息
  1. 修改femu启动脚本

先把下载下来的虚拟机镜像文件拷贝一份,这样一份用作target,一份用作host。

cd ~/images
mv u20s.qcow2 femu-host.qcow2
cp femu-host.qcow2 femu-target.qcow2

修改启动脚本 run-blackbox.sh

cd ~/下载/femu/build-femu/ #cd到femu的build文件夹
cp run-blackbox.sh run-blackbox.sh.bak #先备份一下源文件
mv run-blackbox.sh run-blackbox-host.sh #重命名,这个文件用来启动host虚拟机
cp run-blackbox-host.sh run-blackbox-target.sh #拷贝一份,这个文件用来启动target虚拟机

run-blackbox-host.sh 修改为:

#!/bin/bash
# Huaicheng Li 
# Run FEMU as a black-box SSD (FTL managed by the device)

# image directory
IMGDIR=$HOME/images
# Virtual machine disk image
OSIMGF=$IMGDIR/femu-host.qcow2 # 修改镜像文件名称

if [[ ! -e "$OSIMGF" ]]; then
        echo ""
        echo "VM disk image couldn't be found ..."
        echo "Please prepare a usable VM image and place it as $OSIMGF"
        echo "Once VM disk image is ready, please rerun this script again"
        echo ""
        exit
fi

sudo x86_64-softmmu/qemu-system-x86_64 \
    -name "FEMU-BBSSD-VM" \
    -enable-kvm \
    -cpu host \
    -smp 4 \
    -m 4G \
    -device virtio-scsi-pci,id=scsi0 \
    -device scsi-hd,drive=hd0 \
    -drive file=$OSIMGF,if=none,aio=native,cache=none,format=qcow2,id=hd0 \
    -device femu,devsz_mb=4096,femu_mode=1 \
    -net tap,ifname=tap0,script=no,downscript=no \ # 新添加,告诉femu使用桥接模式,网卡选择tap0
    -net nic,macaddr=`dd if=/dev/urandom count=1 2>/dev/null | md5sum | sed 's/^\(.\)\(..\)\(..\)\(..\)\(..\)\(..\).*$/\14:\2:\3:\4:\5:\6/g'` \ # 使用随机的mac地址
    -nographic \
    -qmp unix:./qmp-sock,server,nowait 2>&1 | tee log

run-blackbox-target.sh 修改为:

#!/bin/bash
# Huaicheng Li 
# Run FEMU as a black-box SSD (FTL managed by the device)

# image directory
IMGDIR=$HOME/images
# Virtual machine disk image
OSIMGF=$IMGDIR/femu-target.qcow2 # 修改镜像文件名称

if [[ ! -e "$OSIMGF" ]]; then
        echo ""
        echo "VM disk image couldn't be found ..."
        echo "Please prepare a usable VM image and place it as $OSIMGF"
        echo "Once VM disk image is ready, please rerun this script again"
        echo ""
        exit
fi

sudo x86_64-softmmu/qemu-system-x86_64 \
    -name "FEMU-BBSSD-VM" \
    -enable-kvm \
    -cpu host \
    -smp 4 \
    -m 4G \
    -device virtio-scsi-pci,id=scsi0 \
    -device scsi-hd,drive=hd0 \
    -drive file=$OSIMGF,if=none,aio=native,cache=none,format=qcow2,id=hd0 \
    -device femu,devsz_mb=4096,femu_mode=1 \
    -net tap,ifname=tap1,script=no,downscript=no \ # 新添加,告诉femu使用桥接模式,网卡选择tap1
    -net nic,macaddr=`dd if=/dev/urandom count=1 2>/dev/null | md5sum | sed 's/^\(.\)\(..\)\(..\)\(..\)\(..\)\(..\).*$/\14:\2:\3:\4:\5:\6/g'` \ # 使用随机的mac地址
    -nographic \
    -qmp unix:./qmp-sock,server,nowait 2>&1 | tee log
  1. 启动femu
cd ~/下载/femu/build-femu/ #cd到femu的build文件夹
./run-blackbox-host.sh #启动host虚拟机
./run-blackbox-target.sh #启动target虚拟机

femu会在虚拟机中模拟出一个4G的SSD。

2.2 搭建soft-roce环境(host和target虚拟机都要执行)

  1. 安装必要的库和工具
sudo apt-get install libibverbs1 ibverbs-utils librdmacm1 libibumad3 ibverbs-providers rdma-core iproute2 perftest
  1. 配置RXE网卡
modprobe rdma_rxe # 加载内核驱动
sudo rdma link add rxe_0 type rxe netdev ens3 # 把以太网卡绑定模拟成RDMA网卡,ens3是以太网卡的名称,可以使用ifconfig查看自己的网卡名称

执行一下 ibv_devices 查看虚拟RDMA设备的信息。

2.3 配置target端

  1. 加载驱动
sudo modprobe nvmet
sudo modprobe nvmet-rdma
sudo modprobe nvme-rdma
  1. 创建一个NVM子系统,名为nvme-subsystem-target,并且允许所有主机访问该NVM子系统
mkdir /sys/kernel/config/nvmet/subsystems/nvm-subsystem-target
cd /sys/kernel/config/nvmet/subsystems/nvm-subsystem-target
echo 1 > attr_allow_any_host #允许所有主机访问
  1. 创建一个namespace,namespace的ID是1
mkdir namespaces/1
cd namespaces/1
  1. 将一个已有的NVM介质/dev/nvme0n1挂载到刚刚创建的命名空间下
echo -n /dev/nvme0n1> device_path
echo 1 > enable
  1. 创建一个传输端口,端口号为1,并设置端口属性
mkdir /sys/kernel/config/nvmet/ports/1
cd /sys/kernel/config/nvmet/ports/1
echo "192.168.13.147" > addr_traddr #给端口绑定IP地址,使用自己的IP替换
echo rdma > addr_trtype
echo 4420 > addr_trsvcid
echo ipv4 > addr_adrfam
  1. 将传输端口绑定到NVM子系统上
ln -s /sys/kernel/config/nvmet/subsystems/nvm-subsystem-target /sys/kernel/config/nvmet/ports/1/subsystems/nvm-subsystem-target
  1. 验证配置是否成功
dmesg -T| grep "enabling port"

如果一切成功的话,会看到NVM子系统正在监听4420端口。

image-20220512204046684

2.3 配置host端

# 探测 192.168.13.147 机器上 4420 端口 nvme ssd
nvme discover -t rdma -q nvme-subsystem-host -a 192.168.13.147 -s 4420

# 连接 192.168.13.147 4420 端口 nvme ssd
nvme connect -t rdma -q nvme-subsystem-target -n nvme-subsystem-target  -a 192.168.13.147 -s 4420

# 与target 端 nvme ssd 断开连接
nvme disconnect -n nvme-subsystem-name

成功的话,会看到NVM子系统正在监听4420端口。

[外链图片转存中…(img-tT7jIfcU-1652521728045)]

2.3 配置host端

# 探测 192.168.13.147 机器上 4420 端口 nvme ssd
nvme discover -t rdma -q nvme-subsystem-host -a 192.168.13.147 -s 4420

# 连接 192.168.13.147 4420 端口 nvme ssd
nvme connect -t rdma -q nvme-subsystem-target -n nvme-subsystem-target  -a 192.168.13.147 -s 4420

# 与target 端 nvme ssd 断开连接
nvme disconnect -n nvme-subsystem-name

你可能感兴趣的:(存储笔记,网络协议)