为基于使用 mlx5 驱动的网卡写一个 Linux 上的 SR-IOV 教程,顺带写一点遇到的问题。

mlx5 网卡列表

mlx5 是 mlx5_core 驱动的简称,使用此驱动的网卡主要是 NVIDIA Mellanox ConnectX-4 或者更新的系列,具体应该有下面这些,其中常见网卡型号有:CX4121A、CX542B

  • ConnectX-4
  • ConnectX-4 Lx
  • ConnectX-5
  • ConnectX-6
  • ConnectX-6 Dx
  • ConnectX-7

查看网卡使用的驱动,使用该命令:lspci -nnk | grep -A3 -i eth

开启 SR-IOV

重启设备可能会导致下面的更改被复原。请参考文章后半部分的 固化 章节来持久化配置。

先使用 lspci | grep Mellanox 查找网卡的 PCI 设备位置

1
2
3
4
5
$ lspci | grep Mellanox
01:00.0 Ethernet controller ... [ConnectX-6]
01:00.1 Ethernet controller ... [ConnectX-6]
02:00.0 Ethernet controller ... [ConnectX-6]
02:00.1 Ethernet controller ... [ConnectX-6]

以上是插入了 2 块 CX6 网卡、每块网卡有 2 个网络接口的典型输出。通过 PCI 设备 ID 可看出前两个、后两个设备都分别是同一张网卡的不同接口。

然后我们使用下面的命令来开启 SR-IOV:

1
echo 4 > /sys/bus/pci/devices/0000\:01\:00.0/sriov_numvfs

将 4 替换为你需要的 SR-IOV VF 数量,然后将 0000\:01\:00.0 替换为网卡接口的 PCI 位置(这里通常可以用 Tab 补全)

有多个网卡或接口时,对每个接口分别设置。

若要撤销更改(删除 SR-IOV 设备),将 VF 数量设为 0。

执行这条命令时卡了很久?
此命令执行耗时一般不会太长(每个 VF 大约需要不到 1 秒来配置),但是在少数机器上,此命令可能需要几分钟甚至几个小时才能完成,且执行完成后部分 VF 不能正常使用。

此故障一般是主板问题导致:你的设备不完整支持 ARI,后期我会单独写一篇文章解释。

解决方案:确保每张网卡(不是每个端口)的 VF 数不超过 6,或者换个主板

PCI 设备路径

开始之前,先普及一下 PCI 设备路径的组成(如果你已经有了此部分知识,跳过本章):

PCI 设备路径(也有 PCI 位置 或者类似的称呼)类似 0000:01:00.0,是由冒号分隔的十六进制数,每一段的含义是:

1
[domain:]<bus>:<device>[.function]

具体的:

  • Domain(域):
    多数系统只有一个 domain 即 0000(例外一般是虚拟机),用于支持大型系统或多个 PCI host bridge。
  • Bus Number(总线):
    PCI 总线的编号,主板上的每个 PCI 控制器或桥接芯片可能生成一个或多个总线。
  • Device Number(设备):
    该总线上的设备编号(通常为 0~31),一个总线最多可挂载 32 个设备。
  • Function Number(功能):
    该设备的功能号(0~7),一个设备最多支持 8 个功能。

其中 domain 有时可以省略(指代默认的 0000),function 有时也可以省略(指代该设备的全部 function)

VF 的 PCI 位置

此章节只有演示讲解,你不需要执行任何查询信息以外的命令

为了方便解释,本节展示使用 1 张双接口网卡(两个 PF),对每个接口开启 4 个 VF 进行 SR-IOV 的配置。

为两个 PF 分别创建 4 个 VF:

1
2
echo 4 > /sys/bus/pci/devices/0000\:01\:00.0/sriov_numvfs
echo 4 > /sys/bus/pci/devices/0000\:01\:00.1/sriov_numvfs

创建后重新使用 lspci | grep Mellanox 查找网卡的 PCI 设备位置

1
2
3
4
5
6
7
8
9
10
11
$ lspci | grep Mellanox
01:00.0 Ethernet controller ... [ConnectX-6] # 端口 0
01:00.1 Ethernet controller ... [ConnectX-6] # 端口 1
01:00.2 Ethernet controller ... [ConnectX-6 Virtual Function]
01:00.3 Ethernet controller ... [ConnectX-6 Virtual Function]
01:00.4 Ethernet controller ... [ConnectX-6 Virtual Function]
01:00.5 Ethernet controller ... [ConnectX-6 Virtual Function]
01:00.6 Ethernet controller ... [ConnectX-6 Virtual Function]
01:00.7 Ethernet controller ... [ConnectX-6 Virtual Function]
01:01.0 Ethernet controller ... [ConnectX-6 Virtual Function]
01:01.1 Ethernet controller ... [ConnectX-6 Virtual Function]

可以看到 VF 已经成功创建(01:00.201:01.1)。

由于 PCI 每个设备只能拥有 8 个功能,PCIe 3.0 引入了 ARI 技术,原理大体上是将设备和功能合并到一起,功能数量从 8 个扩展到 256 个。

使用 ARI 后,01:00.0 网卡创建的第 9 个功能路径会显示为 01:01.0(功能号满 8 就对设备号进位)

由于上面我们先对端口 0 设置 4 个 VF,然后再是端口 1,所以按顺序:01:00.201:00.5 是端口 0 的 VF,其余则是端口 1 的 VF。VF 设备路径的顺序取决于创建 VF 时命令执行顺序。

SR-IOV 下的网卡名

默认规则产生的网卡名会比较长,如果你觉得有必要,可以自己设置个网卡命名规则(具体方法后续补充在这里)。

默认规则下:

  • PF
    一般命名类似 enp1s0f0np0
    p[PCI Bus ID]s[PCI Device ID]f[PCI Function ID]n[pX]
    n 后面(pX)是网卡驱动提供的网卡名字段,从 0 开始
  • VF
    一般命名类似 enp1s0f0npf0vf1
    p[PCI Bus ID]s[PF PCI Device ID]f[PF PCI Function ID]n[pfXvfY]
    前面使用 PF 的 ID,n 后面(pfXvfY)同样是网卡驱动提供的网卡名字段,从 0 开始

为 VF 设置 MAC 地址

重启设备可能会导致下面的更改被复原。请参考文章后半部分的 固化 章节来持久化配置。

  • 如果该 VF 需要直接在宿主机使用,或者用于容器如 LXC,使用该命令:
    ip link set dev <VF 网卡名> address AA:BB:CC:DD:EE:FF
    执行此命令需要确保 VF 已经加载驱动(默认已加载)
  • 如果该 VF 用于 QEMU 虚拟机(包括 KVM、Proxmox 等),使用该命令:
    ip link set <PF 网卡名> vf <VF ID> AA:BB:CC:DD:EE:FF
    VF ID 即该 PF 上的第 n 个 VF,从 0 开始
    如果已加载驱动,重新加载驱动后才能在宿主机生效(虚拟机上使用不需要关心这个)

配置 E-Switch 分载

ConnectX-5 或更新的网卡支持 E-Switch 分载功能,可以部分改善 PF-VF 的交换性能并降低占用,并解决部分情况下宿主机 VF 无法和虚拟机 VF 正常通信的问题,可以按下面的方法开启 E-Switch 分载:

重启设备可能会导致下面的更改被复原。请参考文章后半部分的 固化 章节来持久化配置。

已知问题
开启 E-Switch 分载会导致 FreeBSD 无法加载 VF 网卡驱动。
目前未找到解决方案。

1
devlink dev eswitch set pci/0000:01:00.0 mode switchdev

替换 0000:01:00.0 为实际 PF 的设备路径。

若要撤销更改,将 switchdev 替换为 legacy 执行。

执行前,请确保没有 VF 正在使用,否则卸载 VF 时会报错。建议在创建 VF 之前就执行此操作。

配合 OpenVSwitch(OvS) 使用时,建议执行:

重启设备不会导致下面的更改被复原。该命令不需要固化。

1
ovs-vsctl set Open_vSwitch . other_config:hw-offload=true

若要撤销更改,使用:

1
ovs-vsctl remove Open_vSwitch . other_config hw-offload

Proxmox VE 中 VM 添加 SR-IOV 网卡

1
qm set VMID -hostpci0 0000:01:00.2,pcie=1

VMID 替换为虚拟机的 ID,hostpci0 中的 0 替换为虚拟机内 PCI 设备位置(没有什么具体要求,数字不冲突即可),0000:01:00.2 替换为 VF 的 PCI 位置。

也可以通过网页后台来配置:

Proxmox add PCIe

Proxmox VE 中 LXC 添加 SR-IOV 网卡

1
2
3
lxc.net.0.name: lxc
lxc.net.0.type: phys
lxc.net.0.link: enp1s0f0npf0vf1

name 字段请随意设置,此参数在使用 SR-IOV 时是个摆设(如果需要设置 LXC 内的网卡名,请直接在宿主机修改 VF 网卡名);link 设置为 VF 的网卡名

如果 LXC 内添加了其他网络接口(无论是否是 SR-IOV),请注意 net 后的编号不要冲突

固化

/opt/local-sriov/init.sh 或者你喜欢的地方创建一个脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash

# Set E-Switch Offload
devlink dev eswitch set pci/0000:01:00.0 mode switchdev
devlink dev eswitch set pci/0000:01.00.1 mode switchdev

# Create SR-IOV VFs
# Config:
# 2 VFs on Port 0
# 0 VF on Port 1
echo 2 > /sys/bus/pci/devices/0000\:01\:00.0/sriov_numvfs

# Set IP for each VF
ip link set ens0f0 vf 0 mac AA:AA:AA:AA:AA:AA # VM 100
ip link set dev ens0f0v1 address BB:BB:BB:BB:BB:BB # CT 101

根据实际情况,自行修改脚本中的参数。

chmod +x /opt/local-sriov/init.sh,然后确保此脚本在接口启动时执行。

ifupdown(2/ng) 为例,修改 /etc/network/interfaces

1
2
3
4
auto vmbr0
iface vmbr0 inet static
(原配置保持不变)
post-up sleep 5 && /opt/local-sriov/init.sh