2. 设备直接分配(VT-d)

  • 1. VT-d概述
    • 1.1. 3种客户机设备类型
    • 1.2. VT-d的硬件支持和软件使用
    • 1.3. VT-d的3个缺点和解决方案
  • 2. VFIO简介
    • 2.1. VFIO相对于pci-stub的改进
  • 3. VT-d环境配置
    • 3.1. 硬件支持和BIOS设置
    • 3.2. 宿主机内核的配置
      • 3.2.1. VT-d相关内核编译选项以及启动参数
      • 3.2.2. VFIO相关内核编译选项
      • 3.2.3. 系统检查
    • 3.3. 在宿主机中隐藏设备
    • 3.4. 通过QEMU命令行分配设备给客户机
  • 4. VT-d操作示例
    • 4.1. 网卡直接分配
    • 4.2. 硬盘控制器直接分配
    • 4.3. USB控制器直接分配
    • 4.4. VGA显卡直接分配
  • 5. SR-IOV技术
    • 5.1. SR-IOV概述
      • 5.1.1. SRIOV原理
      • 5.1.2. SRIOV的硬件和软件支持
      • 5.1.3. PF生成VF的两种方式
        • 5.1.3.1. sysfs动态生成
        • 5.1.3.2. PF驱动加载参数
      • 5.1.4. SR-IOV的优缺点
    • 5.2. SR-IOV操作示例
    • 5.3. SR-IOV使用问题解析
      • 5.3.1. VF在客户机中MAC地址全为零
      • 5.3.2. Windows客户机中关于VF的驱动程序
      • 5.3.3. 少数网卡的VF在少数Windows客户机中不工作

1. VT-d概述

1.1. 3种客户机设备类型

在QEMU/KVM中,客户机可以使用的设备大致可分为如下3种类型

1)Emulated device:QEMU纯软件模拟的设备,比如-device rt8139等。

2)virtio device:实现VIRTIO API半虚拟化驱动的设备,比如-device virtio-net-pci等。

3)PCI device assignment:PCI设备直接分配

模拟I/O设备方式的优点是对硬件平台依赖性较低,可以方便地模拟一些流行的和较老久的设备,不需要宿主机和客户机的额外支持,因此兼容性高;而其缺点是I/O路径较长,VM-Exit次数很多,因此性能较差。一般适用于对I/O性能要求不高的场景,或者模拟一些老旧遗留(legacy)设备(如RTL8139的网卡)。

virtio半虚拟化设备方式的优点是实现了VIRTIO API,减少了VM-Exit次数,提高了客户机I/O执行效率,比普通模拟I/O的效率高很多;而其缺点是需要客户机中与virtio相关驱动的支持(较老的系统默认没有自带这些驱动,Windows系统中需要额外手动安装virtio驱动),因此兼容性较差,而且I/O频繁时CPU使用率较高。

PCI设备直接分配(Device Assignment,或PCI pass-through),它允许将宿主机中的物理PCI(或PCI-E)设备直接分配给客户机完全使用,这正是本节要介绍的重点内容。兼具高性能高兼容性.

1.2. VT-d的硬件支持和软件使用

较新的x86架构的主要硬件平台(包括服务器级、桌面级)都已经支持设备直接分配,其中Intel定义的I/O虚拟化技术规范为“Intel(R)Virtualization Technology for Directed I/O”(VT-d),而AMD的I/O虚拟化技术规范为“AMD-Vi”(也叫作IOMMU)。

KVM虚拟机支持将宿主机中的PCIPCI-E设备附加到虚拟化的客户机. 在KVM中通过VT-d技术使用一个PCI-E网卡的系统架构示例如图6-12所示。

运行在支持VT-d平台上的QEMU/KVM,可以分配网卡磁盘控制器USB控制器VGA显卡等供客户机直接使用。而为了设备分配的安全性,还需要中断重映射(interrupt remapping!!!)的支持。

尽管在使用qemu命令行进行设备分配时并不直接检查中断重映射功能是否开启,但是在通过一些工具使用KVM时(如libvirt!!!)默认需要有中断重映射的功能支持,才能使用VT-d分配设备供客户机使用。

1.3. VT-d的3个缺点和解决方案

不过,VT-d也有自己的缺点,

  • 一台服务器主板上的空间比较有限,允许添加的PCI和PCI-E设备是有限的,如果一台宿主机上有较多数量的客户机,则很难向每台客户机都独立分配VT-d的设备。

  • 另外,大量使用VT-d独立分配设备给客户机,导致硬件设备数量增加,这会增加硬件投资成本

为了避免这两个缺点,可以考虑采用如下两个方案:

  • 一是在一台物理宿主机上,仅对I/O(如网络性能要求较高少数客户机使用VT-d直接分配设备(如网卡),而对其余的客户机使用纯模拟(emulated)或使用virtio,以达到多个客户机共享同一个设备的目的;
  • 二是对于网络I/O的解决方法,可以选择SR-IOV,使一个网卡产生多个独立的虚拟网卡,将每个虚拟网卡分别分配给一个客户机使用,这也正是后面6.2.5节要介绍的内容。

另外,设备直接分配还有一个缺点是,对于使用VT-d直接分配了设备的客户机,其动态迁移功能将会受限,不过也可以用热插拔libvirt工具等方式来缓解这个问题,详细内容将在8.1.5节介绍。

2. VFIO简介

在上一版中,VT-d部分依然是以pci-stub模块为例讲解的。

Kernel 3.10发布,VFIO正式被引入,取代了原来的pci-stub的VT-d方式。

2.1. VFIO相对于pci-stub的改进

与Legacy KVM Device Assignment(使用pci-stub driver)相比,VFIO(Virtual Function IO)最大的改进就是隔离了设备之间的DMA和中断!!!,以及对IOMMU Group!!!的支持,从而有了更好的安全性。

IOMMU Group可以认为是对PCI设备的分组每个group里面的设备被视作IOMMU可以操作的最小整体;换句话说,同一个IOMMU Group里的设备不可以分配给不同的客户机

在以前的Legacy KVM Device Assignment中,并不会检查这一点,而后面的操作却注定是失败的。新的VFIO会检查并及时报错。

另外,新的VFIO架构也做到了平台无关,有更好的可移植性。

本书后续就以VFIO为例讲解VT-d。

3. VT-d环境配置

在KVM中使用VT-d技术进行设备直接分配,需要以下几方面的环境配置。

3.1. 硬件支持和BIOS设置

目前市面上的x86硬件平台基本都支持VT-d,包括服务器平台Xeon以及桌面级的酷睿系列。

除了在硬件平台层面对VT-d支持之外,还需要在BIOS将VT-d功能打开,使其处于“Enabled”状态。由于各个BIOS和硬件厂商的标识的区别,VT-d在BIOS中设置选项的名称也有所不同。笔者见到BIOS中VT-d设置选项一般为“Intel(R)VT for Directed I/O”或“Intel VT-d”等,在图3-2中已经演示了在BIOS设置中打开VT-d选项的情况。

3.2. 宿主机内核的配置

宿主机系统中,内核也需要配置相应的选项。

3.2.1. VT-d相关内核编译选项以及启动参数

在RHEL 7自带的Kernel config中,也都已经使这些类似的配置处于打开状态

CONFIG_GART_IOMMU=y         #AMD平台相关
# CONFIG_CALGARY_IOMMU is not set      #IBM平台相关
CONFIG_IOMMU_HELPER=y
CONFIG_VFIO_IOMMU_TYPE1=m
CONFIG_VFIO_NOIOMMU=y
CONFIG_IOMMU_API=y
CONFIG_IOMMU_SUPPORT=y
CONFIG_IOMMU_IOVA=y
CONFIG_AMD_IOMMU=y            #AMD平台的IOMMU设置
CONFIG_AMD_IOMMU_STATS=y
CONFIG_AMD_IOMMU_V2=m
CONFIG_INTEL_IOMMU=y         #Intel平台的VT-d设置
# CONFIG_INTEL_IOMMU_DEFAULT_ON is not set#Intel平台的VT-d是否默认打开。这里没有选上,需要在kernel boot parameter中加上“intel_iommu=on”
CONFIG_INTEL_IOMMU_FLOPPY_WA=y
# CONFIG_IOMMU_DEBUG is not set
# CONFIG_IOMMU_STRESS is not set

CONFIG_INTEL_IOMMU_DEFAULT_ON, 表明的是Intel平台的VT-d是否默认打开。这里没有选上,需要在kernel boot parameter中加上“intel_iommu=on

而在较旧的Linux内核(3.0版本及以下,如2.6.32版本)中,应该配置如下几个VT-d相关的配置选项。

CONFIG_DMAR=y
# CONFIG_DMAR_DEFAULT_ON is not set   #本选项可设置为y,也可不设置
CONFIG_DMAR_FLOPPY_WA=y
CONFIG_INTR_REMAP=y

3.2.2. VFIO相关内核编译选项

另外,为了配合接下来的第3步设置(用于隐藏设备),还需要配置vfio-pci这个内核模块,相关的内核配置选项如下。

在RHEL 7默认内核中,都将CONFIG_KVM_VFIO配置为y(直接编译到内核),其他的功能配置为模块(m)。

CONFIG_VFIO_IOMMU_TYPE1=m
CONFIG_VFIO=m
CONFIG_VFIO_NOIOMMU=y         #支持用户空间的VFIO框架
CONFIG_VFIO_PCI=m
# CONFIG_VFIO_PCI_VGA is not set      #这个是for显卡的VT-d
CONFIG_VFIO_PCI_MMAP=y
CONFIG_VFIO_PCI_INTX=y
CONFIG_KVM_VFIO=y

3.2.3. 系统检查

在启动宿主机系统(这里需要手动修改grub)后,可以通过内核的打印信息来检查VT-d是否处于打开可用状态,如下所示:

[root@kvm-host ~]# dmesg | grep -i dmar
[    0.000000] ACPI: DMAR 000000007b6a0000 00100 (v01 INTEL   S2600WT 00000001 INTL 20091013)
[    0.000000] DMAR: IOMMU enabled
[    0.303666] DMAR: Host address width 46
[    0.303670] DMAR: DRHD base: 0x000000fbffc000 flags: 0x0
[    0.303683] DMAR: dmar0: reg_base_addr fbffc000 ver 1:0 cap 8d2078c106f0466 ecap
     f020de
[    0.303686] DMAR: DRHD base: 0x000000c7ffc000 flags: 0x1
[    0.303696] DMAR: dmar1: reg_base_addr c7ffc000 ver 1:0 cap 8d2078c106f0466 ecap
     f020de
[    0.303699] DMAR: RMRR base: 0x0000007a3e3000 end: 0x0000007a3e5fff
[    0.303702] DMAR: ATSR flags: 0x0
[    0.303707] DMAR-IR: IOAPIC id 10 under DRHD base  0xfbffc000 IOMMU 0
[    0.303710] DMAR-IR: IOAPIC id 8 under DRHD base  0xc7ffc000 IOMMU 1
[    0.303713] DMAR-IR: IOAPIC id 9 under DRHD base  0xc7ffc000 IOMMU 1
[    0.303716] DMAR-IR: HPET id 0 under DRHD base 0xc7ffc000
[    0.303719] DMAR-IR: Queued invalidation will be enabled to support x2apic and 
     Intr-remapping.
[    0.304885] DMAR-IR: Enabled IRQ remapping in x2apic mode
[   36.384332] DMAR: dmar0: Using Queued invalidation
[   36.384603] DMAR: dmar1: Using Queued invalidation
[   36.384898] DMAR: Setting RMRR:
[   36.384965] DMAR: Setting identity map for device 0000:00:1a.0 [0x7a3e3000 - 
    0x7a3e5fff]
[   36.385067] DMAR: Setting identity map for device 0000:00:1d.0 [0x7a3e3000 - 
    0x7a3e5fff]
[   36.385140] DMAR: Prepare 0-16MiB unity mapping for LPC
[   36.385177] DMAR: Setting identity map for device 0000:00:1f.0 [0x0 - 0xffffff]
[   36.385221] DMAR: Intel(R) Virtualization Technology for Directed I/O
[root@kvm-host ~]# dmesg | grep -i iommu
...
[    0.000000] DMAR: IOMMU enabled
[    0.303707] DMAR-IR: IOAPIC id 10 under DRHD base  0xfbffc000 IOMMU 0
[    0.303710] DMAR-IR: IOAPIC id 8 under DRHD base  0xc7ffc000 IOMMU 1
[    0.303713] DMAR-IR: IOAPIC id 9 under DRHD base  0xc7ffc000 IOMMU 1
[   36.385596] iommu: Adding device 0000:ff:08.0 to group 0
[   36.385674] iommu: Adding device 0000:ff:08.2 to group 0
[   36.385751] iommu: Adding device 0000:ff:08.3 to group 0
[   36.386041] iommu: Adding device 0000:ff:09.0 to group 1
[   36.386119] iommu: Adding device 0000:ff:09.2 to group 1
[   36.386196] iommu: Adding device 0000:ff:09.3 to group 1
...(iommu grouping)
[   36.422115] iommu: Adding device 0000:80:05.2 to group 49
[   36.422257] iommu: Adding device 0000:80:05.4 to group 49

如果只有“DMAR:IOMMU enabled”输出,则需要检查BIOS中VT-d是否已打开。

3.3. 在宿主机中隐藏设备

使用vfio_pci这个内核模块来对需要分配给客户机的设备进行隐藏

需要通过如下3步来隐藏一个设备。

1)加载vfio-pci驱动(前面已提及将“CONFIG_VFIO_PCI=m”作为内核编译的配置选项),如下所示:

[root@gerrylee ~]# modprobe vfio_pci
[root@gerrylee ~]# lsmod | grep vfio_pci
vfio_pci               41268  0
vfio                   32657  2 vfio_iommu_type1,vfio_pci
irqbypass              13503  2 kvm,vfio_pci
[root@gerrylee ~]# ls /sys/bus/pci/drivers/vfio-pci/
bind  module  new_id  remove_id  uevent  unbind

如果vfio_pci被编译到内核不是作为module,则仅需最后一个命令来检查/sys/bus/pci/drivers/vfio-pci/目录是否存在即可。

2)查看设备的vendor ID和device ID,如下所示(假设此设备的BDF为03:10.3):

[root@kvm-host ~]# lspci -s 03:10.3 -Dn
0000:03:10.3 0200: 8086:1520 (rev 01)

在上面lspci命令行中,

  • -D选项表示在输出信息中显示设备的domain,
  • -n选项表示用数字的方式显示设备的vendor IDdevice ID
  • -s选项表示仅显示后面指定的一个设备的信息。

在该命令的输出信息中,“0000:03:10.3”表示设备在PCI/PCI-E总线中的具体位置,依次是设备的domain(0000)、bus(03)、slot(10)、function(3),其中domain的值一般为0(当机器有多个host bridge时,其取值范围是0~0xffff),bus的取值范围是0~0xff,slot取值范围是0~0x1f,function取值范围是0~0x7,其中后面3个值一般简称为BDF(即bus:device:function)。

在输出信息中,设备的vendor ID是“8086”(“8086”ID代表Intel Corporation),device ID是“1520”(代表i350 VF)。

3)绑定设备到vfio-pci驱动,命令行操作如下所示。

查看它目前的驱动,如不是vfio-pci,则解绑当前驱动,然后绑定到vfio-pci上。

[root@kvm-host ~]# lspci -s 03:10.3 -k
03:10.3 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
Subsystem: Intel Corporation Device 35c4
Kernel driver in use: igbvf
Kernel modules: igbvf

[root@kvm-host ~]# echo 0000:03:10.3 > /sys/bus/pci/drivers/igbvf/unbind

[root@kvm-host ~]# echo -n "8086 1520" > /sys/bus/pci/drivers/vfio-pci/new_id

[root@kvm-host ~]# lspci -s 03:10.3 -k
03:10.3 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
Subsystem: Intel Corporation Device 35c4
Kernel driver in use: vfio-pci
Kernel modules: igbvf

在绑定前,用lspci命令查看BDF为03:10.3的设备使用的驱动是Intel的igbvf驱动,而绑定到vfio_pci后,通过命令可以可查看到它目前使用的驱动是vfio-pci而不是igbvf,其中lspci的 -k选项 表示输出信息中显示正在使用的驱动内核中可以支持该设备的模块

而在客户机不需要使用该设备后,让宿主机使用该设备,则需要将其恢复到使用原本的驱动。

本节的隐藏和恢复设备,利用如下一个Shell脚本可以方便实现

#!/bin/bash
# A script to hide/unhide PCI/PCIe device for KVM  (using 'vfio-pci')
#set -x
hide_dev=0
unhide_dev=0
driver=0

# check if the device exists
function dev_exist()
{
local line_num=$(lspci -s "$1" 2>/dev/null | wc -l)
if [ $line_num = 0 ]; then
    echo "Device $pcidev doesn't exists. Please check your system or your command line."
    exit 1
else
    return 0
fi
}

# output a format "::." (e.g. 0000:01:10.0) of device
function canon() 
{
f='expr "$1" : '.*\.\(.\)''
d='expr "$1" : ".*:\(.*\).$f"'
b='expr "$1" : "\(.*\):$d\.$f"'

if [ 'expr "$d" : '..'' == 0 ]
then
    d=0$d
fi
if [ 'expr "$b" : '.*:'' != 0 ]
then
    p='expr "$b" : '\(.*\):''
    b='expr "$b" : '.*:\(.*\)''
else
    p=0000
fi
if [ 'expr "$b" : '..'' == 0 ]
then
    b=0$b
fi
echo $p:$b:$d.$f
}

# output the device ID and vendor ID
function show_id() 
{
lspci -Dn -s "$1" | awk '{print $3}' | sed "s/:/ /" > /dev/null 2>&1
if [ $? -eq 0 ]; then
    lspci -Dn -s "$1" | awk '{print $3}' | sed "s/:/ /"
else
    echo "Can't find device id and vendor id for device $1"
    exit 1
fi
}

# hide a device using 'vfio-pci' driver/module
function hide_pci()
{
local pre_driver=NULL
local pcidev=$(canon $1)
local pciid=$(show_id $pcidev)

dev_exist $pcidev

if [ -e /sys/bus/pci/drivers/vfio-pci ]; then
    pre_driver=$(basename $(readlink /sys/bus/pci/devices/"$pcidev"/driver))
    echo "Unbinding $pcidev from $pre_driver"
    echo -n "$pciid" > /sys/bus/pci/drivers/vfio-pci/new_id
    echo -n "$pcidev" > /sys/bus/pci/devices/"$pcidev"/driver/unbind

fi

echo "Binding $pcidev to vfio-pci"
echo -n "$pcidev" > /sys/bus/pci/drivers/vfio-pci/bind
return $?
}

function unhide_pci() {
local driver=$2
local pcidev='canon $1'
local pciid='show_id $pcidev'

if [ $driver != 0 -a ! -d /sys/bus/pci/drivers/$driver ]; then
    echo "No $driver interface under sys, return fail"
    exit 1
fi

if [ -h /sys/bus/pci/devices/"$pcidev"/driver ]; then
    local tmpdriver='basename $(readlink /sys/bus/pci/devices/"$pcidev"/driver)'
    if [ "$tmpdriver" = "$driver" ]; then
        echo "$1 has been already bind with $driver, no need to unhide"
        exit 1
    elif [ "$tmpdriver" != "vfio-pci" ]; then
        echo "$1 is not bind with vfio-pci, it is bind with $tmpdriver, no need to unhide"
        exit 1
    else
        echo "Unbinding $pcidev from" $(basename $(readlink /sys/bus/pci/devices/"$pcidev"/driver))
        echo -n "$pcidev" > /sys/bus/pci/drivers/vfio-pci/unbind
        if [ $? -ne 0 ]; then
            return $?
        fi
    fi
fi

if [ $driver != 0 ]; then
    echo "Binding $pcidev to $driver"
    echo -n "$pcidev" > /sys/bus/pci/drivers/$driver/bind
fi

return $?
}

function usage()
{
echo "Usage: vfio-pci.sh -h pcidev "
echo " -h pcidev:  is BDF number of the device you want to hide"
echo " -u pcidev: Optional.  is BDF number of the device you want to unhide."
echo " -d driver: Optional. When unhiding the device, bind the device with . The option should be used together with '-u' option"
echo ""
echo "Example1: sh vfio-pci.sh -h 06:10.0          Hide device 06:10.0 to 'vfio-pci' driver"
echo "Example2: sh vfio-pci.sh -u 08:00.0 -d e1000e   Unhide device 08:00.0 and bind the device with 'e1000e' driver"
exit 1
}

if [ $# -eq 0 ] ; then
usage
fi

# parse the options in the command line
OPTIND=1
while getopts ":h:u:d:" Option
do
case $Option in
    h ) hide_dev=$OPTARG;;
    u ) unhide_dev=$OPTARG;;
    d ) driver=$OPTARG;;
    * ) usage ;;
esac
done

if [ ! -d /sys/bus/pci/drivers/vfio-pci ]; then
modprobe vfio_pci
echo 0
if [ ! -d /sys/bus/pci/drivers/vfio-pci ]; then
    echo "There's no 'vfio-pci' module? Please check your kernel config."
    exit 1
fi
fi

if [ $hide_dev != 0 -a $unhide_dev != 0 ]; then
echo "Do not use -h and -u option together."
exit 1
fi

if [ $unhide_dev = 0 -a $driver != 0 ]; then
echo "You should set -u option if you want to use -d option to unhide a device and bind it with a specific driver"
    exit 1
fi

if [ $hide_dev != 0 ]; then
hide_pci $hide_dev
elif [ $unhide_dev != 0 ]; then
unhide_pci $unhide_dev $driver
fi
exit $?

3.4. 通过QEMU命令行分配设备给客户机

利用qemu-system-x86_64命令行中“-device”选项可以为客户机分配一个设备,配合其中的“vfio-pci!!!” 作为子选项!!! 可以实现设备直接分配

-device driver[,prop[=value][,...]]

其中driver是设备使用的驱动,有很多种类,如

  • vfio-pci表示VFIO方式PCI设备直接分配
  • virtio-balloon-pci(又为virtio-balloon)表示ballooning设备(这与6.1.3节提到的“-balloon virtio”的意义相同)。

prop[=value]是设置驱动的各个属性值

“-device help”可以查看有哪些可用的驱动,“-device driver,help”可查看某个驱动的各个属性值,如下面命令行所示:

[root@kvm-host ~]# qemu-system-x86_64 -device help
Controller/Bridge/Hub devices:
name "i82801b11-bridge", bus PCI
...

USB devices:
......

Storage devices:
......
name "virtio-blk-pci", bus PCI, alias "virtio-blk"
name "virtio-scsi-device", bus virtio-bus
name "virtio-scsi-pci", bus PCI, alias "virtio-scsi"

Network devices:
......
name "e1000e", bus PCI, desc "Intel 82574L GbE Controller"
......
name "virtio-net-device", bus virtio-bus
name "virtio-net-pci", bus PCI, alias "virtio-net"
......

Input devices:
......
name "virtconsole", bus virtio-serial-bus
......

Display devices:
......

Sound devices:
......

Misc devices:
......
name "vfio-pci", bus PCI, desc "VFIO-based PCI device assignment"
name "virtio-balloon-device", bus virtio-bus
name "virtio-balloon-pci", bus PCI, alias "virtio-balloon"
name "virtio-mmio", bus System
name "virtio-rng-device", bus virtio-bus
name "virtio-rng-pci", bus PCI, alias "virtio-rng"

Uncategorized devices:
......

[root@kvm-host ~]# qemu-system-x86_64 -device vfio-pci,help
vfio-pci.x-pci-sub-device-id=uint32
vfio-pci.x-no-kvm-msi=bool
vfio-pci.rombar=uint32
vfio-pci.x-pcie-lnksta-dllla=bool (on/off)
vfio-pci.x-igd-opregion=bool (on/off)
vfio-pci.x-vga=bool (on/off)
vfio-pci.x-pci-vendor-id=uint32
vfio-pci.multifunction=bool (on/off)
vfio-pci.bootindex=int32
vfio-pci.x-req=bool (on/off)
vfio-pci.x-igd-gms=uint32
vfio-pci.romfile=str
vfio-pci.x-no-kvm-intx=bool
vfio-pci.x-pci-device-id=uint32
vfio-pci.host=str (Address (bus/device/function) of the host device, example: 04:10.0)
vfio-pci.x-no-kvm-msix=bool
vfio-pci.x-intx-mmap-timeout-ms=uint32
vfio-pci.command_serr_enable=bool (on/off)
vfio-pci.addr=int32 (Slot and optional function number, example: 06.0 or 06)
vfio-pci.x-pci-sub-vendor-id=uint32
vfio-pci.sysfsdev=str
vfio-pci.x-no-mmap=bool

在-device vfio-pci的属性中,host属性指定分配的PCI设备在宿主机!!! 中的地址(BDF号),addr属性表示设备在客户机!!! 中的PCI的slot编号!!!(即BDF中的D-device的值)。

qemu-system-x86_64命令行工具在启动时分配一个设备给客户机,命令行如下所示:

[root@kvm-host ~]# qemu-system-x86_64 -enable-kvm -smp 4 -m 8G rhel7.3.img -device vfio-pci,host=03:10.3,addr=08

如果要一次性分配多个设备给客户机,只需在qemu-system-x86_64命令行中重复多次“-device vfio-pci,host=$BDF”这样的选项即可。

由于设备直接分配是客户机独占该设备,否则在通过命令行启动另一个客户机再分配这个设备时,会遇到如下的错误提示:

[root@kvm-host ~]# qemu-system-x86_64 -enable-kvm -smp 4 -m 8G rhel7.3.img -device vfio-pci,host=03:10.3,addr=08 -net none
qemu-system-x86_64: -device vfio-pci,host=03:10.3,addr=08: vfio: error opening /dev/vfio/50: Device or resource busy
qemu-system-x86_64: -device vfio-pci,host=03:10.3,addr=08: vfio: failed to get group 50
qemu-system-x86_64: -device vfio-pci,host=03:10.3,addr=08: Device initialization failed

除了在客户机启动时就分配直接分配设备之外,QEUM/KVM还支持设备的热插拔(hot-plug),在客户机运行时!!!添加所需的直接!!!分配设备,这需要在QEMU monitor中运行相应的命令,相关内容将在6.3节中详细介绍。

4. VT-d操作示例

4.1. 网卡直接分配

Intel 82599型号的PCI-E网卡, 这里省略BIOS配置、宿主机内核检查等操作步骤.

1)选择需要直接分配的网卡

[root@kvm-host ~]# lspci -s 05:00.1 -k
05:00.1 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)
Subsystem: Intel Corporation Ethernet Server Adapter X520-2
Kernel driver in use: ixgbe
Kernel modules: ixgbe

2)隐藏该网卡(使用了前面介绍的vfio-pci.sh脚本)。

[root@kvm-host ~]# ./vfio-pci.sh -h 05:00.1
Unbinding 0000:05:00.1 from ixgbe
Binding 0000:05:00.1 to vfio-pci
[root@kvm-host ~]# lspci -s 05:00.1 -k
05:00.1 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)
Subsystem: Intel Corporation Ethernet Server Adapter X520-2
Kernel driver in use: vfio-pci
Kernel modules: ixgbe

3)在启动客户机时分配网卡。

[root@kvm-host ~]# qemu-system-x86_64 -enable-kvm -cpu host -smp 2 -m 4G -drive file= rhel7.img,format=raw,if=virtio,media=disk -device vfio-pci,host=05:00.1 -net none
VNC server running on '::1:5900'

命令行中的“-net none”表示不使用其他的网卡设备(除了直接分配的网卡之外),否则在客户机中将会出现一个直接分配的网卡另一个emulated的网卡

在QEMU monitor中,可以用“info pci”命令查看分配给客户机的PCI设备的情况。

(qemu) info pci
    Bus  0, device   0, function 0:
        Host bridge: PCI device 8086:1237
            id ""

    Bus  0, device   3, function 0:
        Ethernet controller: PCI device 8086:10fb
            IRQ 11.
            BAR0: 64 bit prefetchable memory at 0xfe000000 [0xfe07ffff].
            BAR2: I/O at 0xc040 [0xc05f].
            BAR4: 64 bit prefetchable memory at 0xfe080000 [0xfe083fff].
            id ""

4)在客户机中查看网卡的工作情况。

[root@kvm-guest ~]# lspci -s 00:03.0 -k
00:03.0 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)
Subsystem: Intel Corporation Ethernet Server Adapter X520-2
Kernel driver in use: ixgbe
Kernel modules: ixgbe  
[root@kvm-guest ~]# ethtool -i ens3
driver: ixgbe
version: 4.4.0-k
firmware-version: 0x61bf0001
expansion-rom-version: 
bus-info: 0000:00:03.0
supports-statistics: yes
supports-test: yes
supports-eeprom-access: yes
supports-register-dump: yes
supports-priv-flags: no 
[root@kvm-guest ~]# ping 192.168.0.106 -I ens3 -c 1
PING 192.168.0.106 (192.168.0.106) from 192.168.0.62 ens3: 56(84) bytes of data.
64 bytes from 192.168.0.106: icmp_seq=1 ttl=64 time=0.106 ms

--- 192.168.0.106 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.106/0.106/0.106/0.000 ms

由上面输出信息可知,在客户机中看到的网卡是使用ixgbe驱动Intel 82599网卡(和宿主机隐藏它之前看到的是一样!!! 的),ens3就是该网卡的网络接口,通过ping命令查看其网络是畅通的。

5)关闭客户机后,在宿主机中恢复前面被隐藏的网卡

在客户机关闭或网卡从客户机中虚拟地“拔出”来之后,如果想让宿主机继续使用该网卡,则可以使用vfio-pci.sh脚本来恢复其在宿主机中的驱动绑定情况。操作过程如下所示:

[root@kvm-host ~]# ./vfio-pci.sh -u 05:00.1 -d ixgbe
Unbinding 0000:05:00.1 from vfio-pci
Binding 0000:05:00.1 to ixgbe
[root@kvm-host ~]# lspci -s 05:00.1 -k
05:00.1 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)
Subsystem: Intel Corporation Ethernet Server Adapter X520-2
Kernel driver in use: ixgbe
Kernel modules: ixgbe

在vfio-pci.sh脚本中,“-u $BDF”是指定需要取消隐藏(unhide)的设备,“-d $driver”是指将从vfio_pci的绑定中取消隐藏的设备绑定到另外一个新的驱动(driver)中。由上面的输出信息可知,05:00.1设备使用的驱动vfio-pci变回了ixgbe

4.2. 硬盘控制器直接分配

在现代计算机系统中,一般SATASAS等类型硬盘的控制器(Controller)都是接入PCI(或PCIe)总线上的,所以也可以将硬盘控制器作为普通PCI设备直接分配给客户机使用。

不过当SATA或SAS设备作为PCI设备直接分配时,实际上将其控制器!!!作为一个整体分配到客户机!!! 中,如果宿主机使用的硬盘也连接在同一个SATA或SAS控制器!!!上,则不能将该控制器直接分配给客户机,而是需要硬件平台中至少有两个或以上的STAT或SAS控制器。宿主机系统使用其中一个,然后将另外的一个或多个SATA/SAS控制器完全分配给客户机使用。

下面以一个SATA硬盘控制器为实例,介绍对硬盘控制器的直接分配过程。

1)先在宿主机中查看硬盘设备,然后隐藏需要直接分配的硬盘。其命令行操作如下所示:

[root@kvm-host ~]# ll /dev/disk/by-path/pci-0000\:16\:00.0-sas-0x1221000000000000-lun-0
lrwxrwxrwx 1 root root 9 Sep 24 11:17 /dev/disk/by-path/pci-0000:16:00.0-sas-0x1221000000000000-lun-0 -> ../../sda
[root@kvm-host ~]# ll /dev/disk/by-path/pci-0000\:00\:1f.2-scsi-0\:0\:0\:0
lrwxrwxrwx 1 root root 9 Sep 24 11:17 /dev/disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0 -> ../../sdb

[root@kvm-host ~]# lspci -k -s 16:00.0
16:00.0 SCSI storage controller: LSI Logic / Symbios Logic SAS1078 PCI-Express Fusion-MPT SAS (rev 04)
    Subsystem: Intel Corporation Device 3505
    Kernel driver in use: mptsas
    Kernel modules: mptsas

[root@kvm-host ~]# lspci -k -s 00:1f.2
00:1f.2 SATA controller: Intel Corporation 82801JI (ICH10 Family) SATA AHCI Controller
    Subsystem: Intel Corporation Device 34f8
    Kernel driver in use: ahci
    Kernel modules: ahci

[root@kvm-host ~]# fdisk -l /dev/sdb

Disk /dev/sdb: 164.7 GB, 164696555520 bytes
255 heads, 63 sectors/track, 20023 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x0003e001

    Device Boot      Start         End      Blocks   Id  System
/dev/sdb1   *            1        6528    52428800   83  Linux
/dev/sdb2             6528        7050     4194304   82  Linux swap / Solaris
/dev/sdb3             7050        9600    20480000   83  Linux

[root@kvm-host ~]# df -h
Filesystem            Size  Used Avail Use% Mounted on
/dev/sda1             197G   13G  175G   7% /
tmpfs                  12G   76K   12G   1% /dev/shm

[root@kvm-host ~]# ./vfio-pci.sh -h 00:1f.2
Unbinding 0000:00:1f.2 from ahci
Binding 0000:00:1f.2 to vfio-pci

[root@kvm-host]# lspci -k -s 00:1f.2
00:1f.2 SATA controller: Intel Corporation 82801JI (ICH10 Family) SATA AHCI Controller
    Subsystem: Intel Corporation Device 34f8
    Kernel driver in use: vfio-pci
    Kernel modules: ahci

由上面的命令行输出可知,在宿主机中有两块硬盘sda和sdb,分别对应一个SAS Controller(16:00.0)和一个SATA Controller(00:1f.2),其中sdb大小为160GB,而宿主机系统安装在sda第一个分区(sda1)上。

在用vfio-pci.sh脚本隐藏SATA Controller之前,它使用的驱动是ahci驱动,之后,将其绑定到vfio-pci驱动,为设备直接分配做准备。

2)用如下命令行将STAT硬盘分配(实际是分配STAT Controller)给客户机使用。

[root@kvm-host ~]# qemu-system-x86_64 rhel7.img -m 1024 -device vfio-pci,host= 00:1f.2,addr=0x6 -net nic -net tap
VNC server running on '::1:5900'

3)在客户机启动后,在客户机中查看直接分配得到的SATA硬盘。命令行如下所示:

[root@kvm-guest ~]# fdisk -l /dev/sdb

Disk /dev/sdb: 164.7 GB, 164696555520 bytes
255 heads, 63 sectors/track, 20023 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x0003e001

    Device Boot      Start         End      Blocks   Id  System
/dev/sdb1   *            1        6528    52428800   83  Linux
/dev/sdb2             6528        7050     4194304   82  Linux swap / Solaris
/dev/sdb3             7050        9600    20480000   83  Linux

[root@kvm-guest ~]# ll /dev/disk/by-path/pci-0000\:00\:04.0-scsi-2\:0\:0\:0
lrwxrwxrwx 1 root root 9 Sep 23 23:47 /dev/disk/by-path/pci-0000:00:04.0-scsi-2:0:0:0 -> ../../sdb

[root@kvm-guest ~]# lspci -k -s 00:06.0
00:06.0 SATA controller: Intel Corporation 82801JI (ICH10 Family) SATA AHCI Controller
    Subsystem: Intel Corporation Device 34f8
    Kernel driver in use: ahci
    Kernel modules: ahci

由客户机中的以上命令行输出可知,宿主机中的sdb硬盘(BDF为00:06.0)就是设备直接分配的那个160GB大小的SATA硬盘。

在SATA硬盘成功直接分配到客户机后,客户机中的程序就可以像使用普通硬盘一样对其进行读写操作(也包括磁盘分区等管理操作)。

4.3. USB控制器直接分配

与SATA和SAS控制器类似,在很多现代计算机系统中,USB主机控制器(USB Host Controller)也是接入PCI总线!!! 中去的,所以也可以对USB设备做设备直接分配

同样,这里的USB直接分配也是指对整个USB Host Controller的直接分配,而并不一定仅分配一个USB设备。

常见的USB设备,如U盘键盘鼠标等都可以作为设备直接分配到客户机中使用。

这里以U盘为例来介绍USB直接分配,而USB键盘、鼠标的直接分配也与此类似。在后面介绍VGA直接分配的示例时,也会将鼠标、键盘直接分配到客户机中。

1)在宿主机中查看U盘设备,并将其隐藏起来以供直接分配。命令行操作如下所示:

[root@kvm-host ~]# fdisk -l /dev/sdb

Disk /dev/sdb: 16.0 GB, 16008609792 bytes
21 heads, 14 sectors/track, 106349 cylinders
Units = cylinders of 294 * 512 = 150528 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0xcad4ebea

    Device Boot      Start         End      Blocks   Id  System
/dev/sdb1   *            7      106350    15632384    c  W95 FAT32 (LBA)

[root@kvm-host ~]# ls -l /dev/disk/by-path/pci-0000\:00\:1d.0-usb-0\:1.2\:1.0-scsi-0\:0\:0\:0
lrwxrwxrwx 1 root root 9 Sep 24 06:47 /dev/disk/by-path/pci-0000:00:1d.0-usb-0:1.2:1.0-scsi-0:0:0:0 -> ../../sdb

[root@kvm-host ~]# lspci -k -s 00:1d.0
00:1d.0 USB controller: Intel Corporation C600/X79 series chipset USB2 Enhanced Host Controller #1 (rev 06)
    Subsystem: Intel Corporation Device 35a0
    Kernel driver in use: ehci_hcd
    Kernel modules: ehci-hcd

[root@kvm-host ~]# ./vfio-pci.sh -h 00:1d.0
Unbinding 0000:00:1d.0 from ehci_hcd
Binding 0000:00:1d.0 to vfio-pci

[root@kvm-host ~]# lspci -k -s 00:1d.0
00:1d.0 USB controller: Intel Corporation C600/X79 series chipset USB2 Enhanced Host Controller #1 (rev 06)
    Subsystem: Intel Corporation Device 35a0
    Kernel driver in use: vfio-pci
    Kernel modules: ehci-hcd

宿主机中的命令行输出可知,sdb就是那个使用USB 2.0协议的U盘,它的大小为16GB,其PCI的ID为00:1d.0,在vfio-pci.sh隐藏之前使用的是ehci-hcd驱动,然后被绑定到vfio-pci驱动隐藏起来,以供后面直接分配给客户机使用。

2)将U盘直接分配给客户机使用的命令行如下所示,与普通PCI设备直接分配的操作完全一样。

[root@kvm-host ~]# qemu-system-x86_64 rhel7.img -m 1024 -device vfio-pci,host= 00:1d.0,addr=0x5 -net nic -net tap
VNC server running on '::1:5900'

3)在客户机中,查看通过直接分配得到的U盘的命令行如下:

[root@kvm-guest ~]# df -h
Filesystem            Size  Used Avail Use% Mounted on
/dev/sda1             7.4G  6.3G  734M  90% /
tmpfs                 499M     0  499M   0% /dev/shm

[root@kvm-guest ~]# fdisk -l /dev/sdb

Disk /dev/sdb: 16.0 GB, 16008609792 bytes
21 heads, 14 sectors/track, 106349 cylinders
Units = cylinders of 294 * 512 = 150528 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0xcad4ebea

    Device Boot      Start         End      Blocks   Id  System
/dev/sdb1   *            7      106350    15632384    c  W95 FAT32 (LBA)

[root@kvm-guest ~]# lspci -k -s 00:05.0
00:05.0 USB controller: Intel Corporation C600/X79 series chipset USB2 Enhanced Host Controller #1 (rev 06)
    Subsystem: Intel Corporation Device 35a0
    Kernel driver in use: ehci_hcd

由客户机中的命令行输出可知,sdb就是那个16GB的U盘(BDF为00:05.0),目前使用ehci_hcd驱动。在U盘直接分配成功后,客户机就可以像在普通系统中使用U盘一样直接使用它了。

另外,也有其他的命令行参数(-usbdevice)来支持USB设备的分配。不同于前面介绍的对USB Host Controller的直接分配,-usbdevice参数用于分配单个USB设备。在宿主机中不要对USB Host Controller进行隐藏(如果前面已经隐藏了,可以用“vfio-pci.sh -u 00:1d.0 -d ehci_hcd”命令将其释放出来),用“lsusb”命令查看需要分配的USB设备的信息,然后在要启动客户机的命令行中使用“-usbdevice host:xx”这样的参数启动客户机即可。其操作过程如下:

[root@kvm-host ~]# lsusb
Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 003: ID 0781:5567 SanDisk Corp. Cruzer Blade 
#用于分配的SandDisk的U盘设备
root@kvm-host ~]# qemu-system-x86_64 rhel7.img -m 1024 -usbdevice host:0781:5667 -net nic -net tap
VNC server running on '::1:5900'

4.4. VGA显卡直接分配

在计算机系统中,显卡也是作为一个PCI或PCIe设备接入系统总线之中的。

KVM虚拟化环境中,如果有在客户机中看高清视频和玩高清游戏的需求,也可以将显卡像普通PCI设备一样完全分配给某个客户机使用。

目前,市面上显卡的品牌很多,有NvidiaATI独立显卡品牌,也包括Intel等公司在较新的CPU中集成的GPU模块(具有3D显卡功能)。

显卡也有多种标准的接口类型,如VGA(Video Graphics Array)、DVI(Digital Visual Interface)、HDMI(High-Definition Multimedia Interface)等。

下面以一台服务器上的集成VGA显卡为例,介绍显卡设备的直接分配过程。在此过程中也将USB鼠标和键盘一起分配给客户机,以方便用服务器上直接连接的物理鼠标、键盘操作客户机。

1)查看USB键盘和鼠标的PCI的BDF,查看VGA显卡的BDF。命令行操作如下所示:

[root@kvm-host ~]# dmesg | grep -i mouse
[233824.471274] usb 3-7: Product: HP Mobile USB Optical Mouse
[233824.473781] input: PixArt HP Mobile USB Optical Mouse as /devices/pci0000: 00/0000:00:14.0/usb3/3-7/3-7:1.0/input/input7
[233824.473928] hid-generic 0003:03F0:8607.0006: input,hidraw5: USB HID v1.11 Mouse [PixArt HP Mobile USB Optical Mouse] on usb-0000:00:14.0-7/input0 

[root@kvm-host ~]# dmesg | grep -i keyboard
[246115.530543] usb 3-12: Product: HP Basic USB Keyboard
[246115.536008] input: CHICONY HP Basic USB Keyboard as /devices/pci0000: 00/0000:00:14.0/usb3/3-12/3-12:1.0/input/input8
[246115.587246] hid-generic 0003:03F0:0024.0007: input,hidraw3: USB HID v1.10 Keyboard [CHICONY HP Basic USB Keyboard] on usb-0000:00:14.0-12/input0

[root@kvm-host ~]# lsusb
......
Bus 003 Device 005: ID 03f0:8607 Hewlett-Packard Optical Mobile Mouse
......
Bus 003 Device 006: ID 03f0:0024 Hewlett-Packard KU-0316 Keyboard
......

[root@kvm-host ~]# lsusb -t
/:  Bus 04.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/6p, 5000M
/:  Bus 03.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/15p, 480M
    |__ Port 2: Dev 2, If 0, Class=Human Interface Device, Driver=usbhid, 12M
    |__ Port 7: Dev 5, If 0, Class=Human Interface Device, Driver=usbhid, 1.5M
    |__ Port 9: Dev 3, If 0, Class=Human Interface Device, Driver=usbhid, 12M
    |__ Port 9: Dev 3, If 1, Class=Human Interface Device, Driver=usbhid, 12M
    |__ Port 12: Dev 6, If 0, Class=Human Interface Device, Driver=usbhid, 1.5M
/:  Bus 02.Port 1: Dev 1, Class=root_hub, Driver=ehci-pci/2p, 480M
    |__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/8p, 480M
/:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=ehci-pci/2p, 480M
    |__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/6p, 480M

从上面命令输出可以看出,在笔者环境中,分别有一个HP鼠标(USB接口)和一个HP键盘(USB接口)接在主机上。而它们又都从属于USB bus3根控制器(BDF是0000:00:14.0)。

下面我们来看看这个00:14.0 PCI设备究竟是不是USB根控制器

[root@kvm-host ~]# lspci -s 00:14.0 -v
00:14.0 USB controller: Intel Corporation C610/X99 series chipset USB xHCI Host Controller (rev 05) (prog-if 30 [XHCI])
Subsystem: Intel Corporation Device 35c4
Flags: bus master, medium devsel, latency 0, IRQ 33, NUMA node 0
Memory at 383ffff00000 (64-bit, non-prefetchable) [size=64K]
Capabilities: [70] Power Management version 2
Capabilities: [80] MSI: Enable+ Count=1/8 Maskable- 64bit+
Kernel driver in use: xhci_hcd

果然,它是一个USB3.0的根控制器。后面我们就通过将它VT-d给客户机,进而实现把它下属的HP的USB鼠标键盘(以及其他的下属USB设备)都分配给客户机的目的。

下面我们再来找到VGA设备的BDF号

[root@kvm-host ~]# lspci | grep -i vga
08:00.0 VGA compatible controller: Matrox Electronics Systems Ltd. MGA G200e [Pilot] ServerEngines (SEP1) (rev 05)

[root@kvm-host ~]# lspci -s 08:00.0 -v
08:00.0 VGA compatible controller: Matrox Electronics Systems Ltd. MGA G200e [Pilot] ServerEngines (SEP1) (rev 05) (prog-if 00 [VGA controller])
Subsystem: Intel Corporation Device 0103
Flags: fast devsel, IRQ 19, NUMA node 0
Memory at 90000000 (32-bit, prefetchable) [disabled] [size=16M]
Memory at 91800000 (32-bit, non-prefetchable) [disabled] [size=16K]
Memory at 91000000 (32-bit, non-prefetchable) [disabled] [size=8M]
Expansion ROM at 91810000 [disabled] [size=64K]
Capabilities: [dc] Power Management version 2
Capabilities: [e4] Express Legacy Endpoint, MSI 00
Capabilities: [54] MSI: Enable- Count=1/1 Maskable- 64bit-
Kernel driver in use: mgag200
Kernel modules: mgag200

可以看到笔者主机上的VGA显卡是Matrox公司的G200e型号,BDF为08:00.0。

2)分别将鼠标键盘VGA显卡隐藏起来,以便分配给客户机。命令行操作如下所示:

[root@kvm-host ~]# ./vfio-pci.sh -h 00:14.00
Unbinding 0000:00:14.0 from xhci_hcd
Binding 0000:00:14.0 to vfio-pci 

[root@kvm-host ~]# lspci -s 00:14.0 -k
00:14.0 USB controller: Intel Corporation C610/X99 series chipset USB xHCI Host Controller (rev 05)
Subsystem: Intel Corporation Device 35c4
Kernel driver in use: vfio-pci 

[root@kvm-host ~]# ./vfio-pci.sh -h 08:00.0
Unbinding 0000:08:00.0 from mgag200
Binding 0000:08:00.0 to vfio-pci 

[root@kvm-host ~]# lspci -s 08:00.0 -k
08:00.0 VGA compatible controller: Matrox Electronics Systems Ltd. MGA G200e [Pilot] ServerEngines (SEP1) (rev 05)
Subsystem: Intel Corporation Device 0103
Kernel driver in use: vfio-pci
Kernel modules: mgag200

3)qemu命令行启动一个客户机,将USB3.0根控制器VGA显卡都分配给它。其命令行操作如下所示:

[root@kvm-host ~]# qemu-system-x86_64 -enable-kvm -smp 4 -m 8G rhel7.img -device vfio-pci,host=00:14.0 -device vfio-pci,host=08:00.0 -device virtio-net-pci,netdev=nic0 -netdev bridge,br=virbr0,id=nic0

4)在客户机中查看分配的VGA显卡和USB键盘鼠标,命令行操作如下所示:

[root@kvm-guest ~]# lspci | grep -i usb
00:03.0 USB controller: Intel Corporation C610/X99 series chipset USB xHCI Host Controller (rev 05)
[root@kvm-guest ~]# lspci -s 00:03.0 -v
00:03.0 USB controller: Intel Corporation C610/X99 series chipset USB xHCI Host Controller (rev 05) (prog-if 30 [XHCI])
Subsystem: Intel Corporation Device 35c4
Physical Slot: 3
Flags: bus master, medium devsel, latency 0, IRQ 24
Memory at fe850000 (64-bit, non-prefetchable) [size=64K]
Capabilities: [70] Power Management version 2
Capabilities: [80] MSI: Enable+ Count=1/8 Maskable- 64bit+
Kernel driver in use: xhci_hcd

[root@kvm-guest ~]# lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 004: ID 046b:ff10 American Megatrends, Inc. Virtual Keyboard and Mouse
Bus 001 Device 007: ID 03f0:8607 Hewlett-Packard Optical Mobile Mouse
Bus 001 Device 002: ID 14dd:1005 Raritan Computer, Inc. 
Bus 001 Device 005: ID 03f0:0024 Hewlett-Packard KU-0316 Keyboard
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
[root@kvm-guest ~]# lsusb -t
/:  Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/6p, 5000M
/:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/15p, 480M
    |__ Port 2: Dev 2, If 0, Class=Human Interface Device, Driver=usbhid, 12M
    |__ Port 7: Dev 8, If 0, Class=Human Interface Device, Driver=usbhid, 1.5M
    |__ Port 9: Dev 4, If 0, Class=Human Interface Device, Driver=usbhid, 12M
    |__ Port 9: Dev 4, If 1, Class=Human Interface Device, Driver=usbhid, 12M
    |__ Port 12: Dev 5, If 0, Class=Human Interface Device, Driver=usbhid, 1.5M 
[root@kvm-guest ~]# lspci | grep -i vga
00:02.0 VGA compatible controller: Device 1234:1111 (rev 02)
00:04.0 VGA compatible controller: Matrox Electronics Systems Ltd. MGA G200e [Pilot] ServerEngines (SEP1) (rev 05)
[root@kvm-guest ~]# dmesg | grep -i vga
[    0.000000] Console: colour VGA+ 80x25
[    1.136954] vgaarb: device added: PCI:0000:00:02.0,decodes=io+mem,owns=io+mem, 
     locks=none
[    1.136963] vgaarb: device added: PCI:0000:00:04.0,decodes=io+mem,owns=io+mem,
     locks=none
[    1.136964] vgaarb: loaded
[    1.136965] vgaarb: bridge control possible 0000:00:04.0
[    1.136965] vgaarb: no bridge control possible 0000:00:02.0
[    1.771908] [drm] Found bochs VGA, ID 0xb0c0.
[   13.302791] mgag200 0000:00:04.0: VGA-1: EDID block 0 invalid.

由上面输出可以看出,随着USB根控制器!!!的传入,其下属的所有USB设备!!!(包括我们的目标USB鼠标和键盘)也都传给了客户机(此时宿主机上lsusb就看不到USB3.0根控制器及其从属的USB设备了)。

客户机两个VGA显卡,其中BDF 00:02.0是5.6节提到的QEMU纯软件模拟的Cirrus显卡,而另外的BDF 00:04.0就是设备直接分配得到的GMA G200e显卡,它的信息与在宿主机中查看到的是一样的。从demsg信息可以看到,系统启动后,00:04.0显卡才是最后真正使用的显卡,而00:02.0是不可用的(处于“no bridge control possible”状态)。

另外,本示例在客户机中也启动了图形界面,对使用的显卡进行检查,还可以在客户机中查看Xorg的日志文件:/var/log/Xorg.0.log,其中部分内容如下:

X.Org X Server 1.17.2
Release Date: 2015-06-16
[    24.254] X Protocol Version 11, Revision 0

[    24.257] (II) xfree86: Adding drm device (/dev/dri/card0)
[    24.257] (II) xfree86: Adding drm device (/dev/dri/card1)
[    24.260] (--) PCI:*(0:0:2:0) 1234:1111:1af4:1100 rev 2, Mem @ 0xfb000000/16777216,
     0xfe874000/4096, BIOS @ 0x????????/65536
[    24.260] (--) PCI: (0:0:4:0) 102b:0522:8086:0103 rev 5, Mem @ 0xfc000000/16777216,
     0xfe870000/16384, 0xfe000000/8388608, BIOS @ 0x????????/65536
<!-- 省略中间更多日志输出 -->
(II) VESA: driver for VESA chipsets: vesa
(II) FBDEV: driver for framebuffer: fbdev
(II) Primary Device is: PCI 00@00:04:0

由上面日志Xorg.0.log中的信息可知,X窗口程序检测到两个VGA显卡,最后使用的是BDF为00:04.0的显卡,使用了VESA程序来驱动该显卡。在客户机内核的配置中,对VESA的配置已经编译到内核中去了,因此可以直接使用。

[root@kvm-guest ~]# grep -i vesa /boot/config-3.10.0-514.el7.x86_64 
CONFIG_FB_BOOT_VESA_SUPPORT=y
# CONFIG_FB_UVESA is not set
CONFIG_FB_VESA=y

在本示例中,在RHEL 7.3客户机启动的前期默认使用的是QEMU模拟的Cirrus显卡,而在系统启动完成后打开用户登录界面(启动了X-window图形界面),客户机就自动切换到使用直接分配的设备GMA G200e显卡了,在连接物理显卡的显示器上就出现了客户机的界面。

对于不同品牌的显卡及不同类型的客户机系统,KVM对它们的支持有所不同,其中也存在部分bug。在使用显卡设备直接分配时,可能有的显卡在某些客户机中并不能正常工作,这就需要根据实际情况来操作。另外,在Windows客户机中,如果在“设备管理器”中看到了分配给它的显卡,但是并没有使用和生效,可能需要下载合适的显卡驱动,并且在“设备管理器”中关闭纯软件模拟的那个显卡,而且需要开启设置直接分配得到的显卡,这样才能让接VGA显卡的显示器能显示Windows客户机中的内容。

5. SR-IOV技术

5.1. SR-IOV概述

为了实现多个虚拟机能够共享同一个物理设备的资源,并且达到设备直接分配的性能,PCI-SIG组织发布了SR-IOV(Single Root I/O Virtualization and Sharing)规范,该规范定义个了一个标准化的机制,用以原生地支持实现多个共享的设备(不一定是网卡设备)。

不过,目前SR-IOV(单根I/O虚拟化)最广泛的应用还是在以太网卡设备的虚拟化方面。QEMU/KVM在2009年实现了对SR-IOV技术的支持,其他一些虚拟化方案(如Xen、VMware、Hyper-V等)也都支持SR-IOV了。

5.1.1. SRIOV原理

在详细介绍SR-IOV之前,先介绍一下SR-IOV中引入的两个新的功能(function)类型

1)Physical Function(PF,物理功能):拥有包含SR-IOV扩展能力在内的所有完整的PCI-e功能,其中SR-IOV能力使PF可以配置和管理SR-IOV功能。简言之,PF就是一个普通的PCI-e设备(带有SR-IOV功能),可以放在宿主机中配置和管理其他VF,它本身也可以作为一个完整独立的功能使用。

2)Virtual Function(VF,虚拟功能):由PF衍生而来的“轻量级”的PCI-e功能,包含数据传送所必需的资源,但是仅谨慎地拥有最小化的配置资源。简言之,VF通过PF的配置之后,可以分配到客户机中作为独立功能使用。

SR-IOV为客户机中使用的VF提供了独立的内存空间、中断、DMA流,从而不需要Hypervisor介入数据的传送过程。SR-IOV架构设计的目的是允许一个设备支持多个VF,同时也尽量减小每个VF的硬件成本。Intel有不少高级网卡可以提供SR-IOV的支持,图6-13展示了Intel以太网卡中的SR-IOV的总体架构。 

一个具有SR-IOV功能的设备能够被配置为在PCI配置空间(configuration space)中呈现出多个Function(包括一个PF和多个VF),每个VF都有自己独立的配置空间完整的BAR(Base Address Register,基址寄存器)。

Hypervisor通过将VF实际的配置空间映射到客户机看到的配置空间的方式,实现将一个或多个VF分配给一个客户机。

通过Intel VT-xVT-d等硬件辅助虚拟化技术提供的内存转换技术,允许直接的DMA传输去往或来自一个客户机,从而绕过了Hypervisor中的软件交换机(software switch)。

每个VF在同一个时刻只能被分配到一个客户机中,因为VF需要真正的硬件资源(不同于emulated类型的设备)。在客户机中的VF,表现给客户机操作系统的就是一个完整的普通的设备。

在KVM中,可以将一个或多个VF分配给一个客户机,客户机通过自身的VF驱动程序直接操作设备的VF而不需要Hypervisor(即KVM)的参与,其示意图如图6-14所示。 

5.1.2. SRIOV的硬件和软件支持

为了让SR-IOV工作起来,

  • 需要硬件平台支持Intel VT-xVT-d(或AMD的SVM和IOMMU)硬件辅助虚拟化特性,
  • 还需要有支持SR-IOV规范的设备
  • 当然也需要QEMU/KVM的支持

支持SR-IOV的设备较多,其中Intel有很多中高端网卡支持SR-IOV特性,如Intel 82576网卡(代号“Kawella”,使用igb驱动)、I350网卡(igb驱动)、82599网卡(代号“Niantic”,使用ixgbe驱动)、X540(使用ixgbe驱动)、X710(使用i40e驱动)等。

在宿主机Linux环境中,可以通过“lspci -v\ -s $BDF”的命令来查看网卡PCI信息的“Capabilities”项目,以确定设备是否具备SR-IOV功能。命令行如下所示:

[root@kvm-host ~]# lspci -s 03:00.0 -v
03:00.0 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01)
    Subsystem: Intel Corporation Device 35c4
    Physical Slot: 0
    Flags: bus master, fast devsel, latency 0, IRQ 32, NUMA node 0
    Memory at 91920000 (32-bit, non-prefetchable) [size=128K]
    I/O ports at 2020 [size=32]
    Memory at 91944000 (32-bit, non-prefetchable) [size=16K]
    Capabilities: [40] Power Management version 3
    Capabilities: [50] MSI: Enable- Count=1/1 Maskable+ 64bit+
    Capabilities: [70] MSI-X: Enable+ Count=10 Masked-
    Capabilities: [a0] Express Endpoint, MSI 00
    Capabilities: [100] Advanced Error Reporting
    Capabilities: [140] Device Serial Number 00-1e-67-ff-ff-ed-fb-dd
    Capabilities: [150] Alternative Routing-ID Interpretation (ARI)
    Capabilities: [160] Single Root I/O Virtualization (SR-IOV)
    Capabilities: [1a0] Transaction Processing Hints
    Capabilities: [1c0] Latency Tolerance Reporting
    Capabilities: [1d0] Access Control Services
    Kernel driver in use: igb
    Kernel modules: igb

一个设备可支持多个VF,PCI-SIG的SR-IOV规范指出每个PF最多能拥有256个VF,而实际支持的VF数量是由设备的硬件设计及其驱动程序共同决定的。

前面举例的几个网卡,其中使用“igb”驱动的82576、I350等千兆(1G)以太网卡的每个PF支持最多7个VF,而使用“ixgbe”驱动的82599、X540等万兆(10G)以太网卡的每个PF支持最多63个VF

宿主机系统中可以用“modinfo”命令来查看某个驱动的信息,其中包括驱动模块的可用参数。如下命令行演示了常用igb和ixgbe驱动的信息。

[root@kvm-host ~]# modinfo igb
...
parm: max_vfs:Maximum number of virtual functions to allocate per physical function (uint)
parm: debug:Debug level (0=none,...,16=all) (int) 

[root@kvm-host ~]# modinfo ixgbe
...
parm: max_vfs:Maximum number of virtual functions to allocate per physical function - default is zero and maximum value is 63 (uint)
parm: allow_unsupported_sfp:Allow unsupported and untested SFP+ modules on 82599-based adapters (uint)
parm:  debug:Debug level (0=none,...,16=all) (int)

通过sysfs中开放出来的设备的信息,我们可以知道具体某款网卡设备到底支持多少VFsriov_totalvfs),以及当前有多少VFsriov_numvfs)。

[root@kvm-host ~]# cat /sys/bus/pci/devices/0000\:03\:00.0/sriov_totalvfs
7
[root@kvm-host ~]# cat /sys/bus/pci/devices/0000\:03\:00.0/sriov_numvfs
0

5.1.3. PF生成VF的两种方式

如何让PF衍生出VF呢?

5.1.3.1. sysfs动态生成

推荐:通过sysfs动态生成及增减。

如上面例子中已经看到,在设备的sysfs entry中有两个入口sriov_totalvfssriov_numvfs,分别用于表面网卡最多支持多少VF,已经实时有多少VF。

我们可以通过如下命令让PF(BDF 03:00.3)衍生出5个VF:

[root@kvm-host ~]# echo 5 > /sys/bus/pci/devices/0000\:03\:00.3/sriov_numvfs
[root@kvm-host ~]# cat /sys/bus/pci/devices/0000\:03\:00.3/sriov_numvfs
5

[root@kvm-host ~]# lspci | grep -i eth
03:00.0 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01)
03:00.3 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01)
03:10.3 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:10.7 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:11.3 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:11.7 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:12.3 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)

可以看到,系统中多出来了03:10.3、03:10.7、03:11.3、03:11.7、03:12.3这5个“Intel Corporation I350 Ethernet Controller Virtual Function(rev 01)”设备。

我们可以通过下面的命令来查看PF和VF的所属关系:

[root@kvm-host ~]# ls -l /sys/bus/pci/devices/0000\:03\:00.3/virtfn*
lrwxrwxrwx 1 root root 0 Dec 25 15:33 /sys/bus/pci/devices/0000:03:00.3/virtfn0 -> ../0000:03:10.3
lrwxrwxrwx 1 root root 0 Dec 25 15:33 /sys/bus/pci/devices/0000:03:00.3/virtfn1 -> ../0000:03:10.7
lrwxrwxrwx 1 root root 0 Dec 25 15:33 /sys/bus/pci/devices/0000:03:00.3/virtfn2 -> ../0000:03:11.3
lrwxrwxrwx 1 root root 0 Dec 25 15:33 /sys/bus/pci/devices/0000:03:00.3/virtfn3 -> ../0000:03:11.7
lrwxrwxrwx 1 root root 0 Dec 25 15:33 /sys/bus/pci/devices/0000:03:00.3/virtfn4 -> ../0000:03:12.3

5.1.3.2. PF驱动加载参数

不推荐:通过PF驱动(如igb,ixgbe)加载时候指定max_vfs参数

在前面一节中,我们通过“modinfo”命令查看了igb和ixgbe驱动,知道了其“max_vfs”参数就是决定加载时候启动多少个VF。如果当前系统还没有启用VF,则需要卸载掉驱动重新加载驱动(加上VF个数的参数)来开启VF。

如下命令行演示了开启igb驱动VF个数的参数的过程及在开启VF之前和之后系统中网卡的状态

[root@kvm-host ~]# lspci | grep -i eth
03:00.0 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01)
03:00.3 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01)

[root@kvm-host ~]# modprobe -r igb; modprobe igb max_vfs=7
 
[root@kvm-host ~]# lspci | grep -i eth
03:00.0 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01)
03:00.3 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01)
03:10.0 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:10.3 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:10.4 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:10.7 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:11.0 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:11.3 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:11.4 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:11.7 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:12.0 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:12.3 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:12.4 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:12.7 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:13.0 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:13.3 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)

由上面的演示可知,BDF 03:00.0和03:00.3是PF,而在通过加了“max_vfs=7”的参数重新加载igb驱动后,对应的VF被启用了,每个PF启用了7个VF。可以通过在modprobe.d中配置相应驱动的启动配置文件,让系统加载驱动时候自动带上max_vfs参数,示例如下所示:

[root@kvm-host ~]# vim /etc/modprobe.d/igb.conf
option igb max_vfs=7

可以发现,

  • 第2种方法不够灵活重新加载驱动会作用于所有适用此驱动的设备
  • 而第1种方法可以不用重新加载驱动,并且可以只作用于某一个PF

另外,值得注意的是,由于VF还是共享和使用对应PF上的部分资源,因此要使SR-IOV的VF能够在客户机中工作,必须保证其对应的PF在宿主机中处于正常工作状态

5.1.4. SR-IOV的优缺点

使用SR-IOV主要有如下3个优势:

1)真正实现了设备的共享(多个客户机共享一个SR-IOV设备的物理端口)。

2)接近于原生系统的高性能(比纯软件模拟和virtio设备的性能都要好)。

3)相比于VT-d,SR-IOV可以用更少的设备支持更多的客户机,可以提高数据中心的空间利用率

而SR-IOV的不足之处有如下两点:

1)对设备有依赖,只有部分PCI-E设备支持SR-IOV(如前面提到的Intel 82576、82599网卡)。

2)使用SR-IOV时,不方便动态迁移客户机(在8.1.4节中会介绍一种绕过这个问题的方法)。

5.2. SR-IOV操作示例

在了解了SR-IOV的基本原理及优劣势之后,本节将以一个完整的示例来介绍在KVM中使用SR-IOV的各个步骤。这个例子是这样的,在笔者的环境中(kvm-host),有一个两口的i350网卡,使用SR-IOV技术将其中的一个PF(BDF 03:00.3)的一个VF分配给一个RHEL 7的客户机使用。

1)在这个PF上派生出若干VF(此处5个)。

[root@kvm-host ~]# lspci | grep -i eth
03:00.0 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01)
03:00.3 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01)

[root@kvm-host ~]# echo 5 > /sys/bus/pci/devices/0000\:03\:00.3/sriov_numvfs

[root@kvm-host ~]# lspci | grep -i eth
03:00.0 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01)
03:00.3 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01)
03:10.3 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:10.7 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:11.3 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:11.7 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
03:12.3 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)

[root@kvm-host ~]# ls -l /sys/bus/pci/devices/0000\:03\:00.3/virtfn*
lrwxrwxrwx 1 root root 0 Dec 25 19:04 /sys/bus/pci/devices/0000:03:00.3/virtfn0 -> ../0000:03:10.3
lrwxrwxrwx 1 root root 0 Dec 25 19:04 /sys/bus/pci/devices/0000:03:00.3/virtfn1 -> ../0000:03:10.7
lrwxrwxrwx 1 root root 0 Dec 25 19:04 /sys/bus/pci/devices/0000:03:00.3/virtfn2 -> ../0000:03:11.3
lrwxrwxrwx 1 root root 0 Dec 25 19:04 /sys/bus/pci/devices/0000:03:00.3/virtfn3 -> ../0000:03:11.7
lrwxrwxrwx 1 root root 0 Dec 25 19:04 /sys/bus/pci/devices/0000:03:00.3/virtfn4 -> ../0000:03:12.3

[root@kvm-host ~]# ip link show
1: lo:  mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
4: virbr0:  mtu 1500 qdisc noqueue state DOWN mode DEFAULT qlen 1000
    link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
17: eno1:  mtu 1500 qdisc mq state UP mode DEFAULT qlen 1000
    link/ether 00:1e:67:ed:fb:dc brd ff:ff:ff:ff:ff:ff
25: eno2:  mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
    link/ether 00:1e:67:ed:fb:dd brd ff:ff:ff:ff:ff:ff
    vf 0 MAC 00:00:00:00:00:00, spoof checking on, link-state auto
    vf 1 MAC 00:00:00:00:00:00, spoof checking on, link-state auto
    vf 2 MAC 00:00:00:00:00:00, spoof checking on, link-state auto
    vf 3 MAC 00:00:00:00:00:00, spoof checking on, link-state auto
    vf 4 MAC 00:00:00:00:00:00, spoof checking on, link-state auto
26: enp3s16f3:  mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT qlen 1000
    link/ether 2e:64:71:17:30:69 brd ff:ff:ff:ff:ff:ff
27: enp3s16f7:  mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT qlen 1000
    link/ether 9a:e4:2e:31:e5:f9 brd ff:ff:ff:ff:ff:ff
28: enp3s17f3:  mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT qlen 1000
    link/ether 3a:8f:85:fd:bf:31 brd ff:ff:ff:ff:ff:ff
29: enp3s17f7:  mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT qlen 1000
    link/ether 92:4b:5b:71:e1:07 brd ff:ff:ff:ff:ff:ff
30: enp3s18f3:  mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT qlen 1000
    link/ether 52:68:c4:b4:f5:e0 brd ff:ff:ff:ff:ff:ff

由以上输出信息可知,03:00.3PF派生出了03:10.3、03:10.7、03:11.3、03:11.7、03:12.3这5个VF。通过ip link show命令,可以看到03:00.3这个PF在宿主机中是连接正常的,同时可以看到它派生出来的5个VF(vf0~vf4)。

2)将其中一个VF(03:10.3)隐藏,以供客户机使用。命令行操作如下:

[root@kvm-host ~]# lspci -s 03:10.3 -k
03:10.3 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
Subsystem: Intel Corporation Device 35c4
Kernel driver in use: igbvf
Kernel modules: igbvf

[root@kvm-host ~]# ./vfio-pci.sh -h 03:10.3
Unbinding 0000:03:10.3 from igbvf
Binding 0000:03:10.3 to vfio-pci

[root@kvm-host ~]# lspci -s 03:10.3 -k
03:10.3 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
Subsystem: Intel Corporation Device 35c4
Kernel driver in use: vfio-pci
Kernel modules: igbvf

这里隐藏的03:10.3这个VF对应的PF是03:00.3,该PF处于可用状态,才能让VF在客户机中正常工作。

3)在命令行启动客户机时分配一个VF网卡。命令行操作如下:

[root@kvm-host ~]# qemu-system-x86_64 -enable-kvm -smp 4 -m 8G rhel7.img -device vfio-pci,host=03:10.3,addr=06 -net none

4)在客户机中查看VF的工作情况。命令行操作如下:

[root@kvm-guest ~]# lspci | grep -i eth
00:06.0 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)

[root@kvm-guest ~]# lspci -s 00:06.0 -v
00:06.0 Ethernet controller: Intel Corporation I350 Ethernet Controller Virtual Function (rev 01)
Subsystem: Intel Corporation Device 35c4
Physical Slot: 6
Flags: bus master, fast devsel, latency 0
Memory at fe000000 (64-bit, prefetchable) [size=16K]
Memory at fe004000 (64-bit, prefetchable) [size=16K]
Capabilities: [70] MSI-X: Enable+ Count=3 Masked-
Capabilities: [a0] Express Endpoint, MSI 00
Kernel driver in use: igbvf
Kernel modules: igbvf

[root@kvm-guest ~]# ifconfig
ens6: flags=4163  mtu 1500
    inet6 fe80::e890:ddff:fe71:ada8  prefixlen 64  scopeid 0x20
    ether ea:90:dd:71:ad:a8  txqueuelen 1000  (Ethernet)
    RX packets 5  bytes 300 (300.0 B)
    RX errors 0  dropped 0  overruns 0  frame 0
    TX packets 0  bytes 912 (912.0 B)
    TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73  mtu 65536
    inet 127.0.0.1  netmask 255.0.0.0
    inet6 ::1  prefixlen 128  scopeid 0x10
    loop  txqueuelen 1  (Local Loopback)
    RX packets 280  bytes 22008 (21.4 KiB)
    RX errors 0  dropped 0  overruns 0  frame 0
    TX packets 280  bytes 22008 (21.4 KiB)
    TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

virbr0: flags=4099  mtu 1500
    inet 192.168.122.1  netmask 255.255.255.0  broadcast 192.168.122.255
    ether 52:54:00:48:d8:d1  txqueuelen 1000  (Ethernet)
    RX packets 0  bytes 0 (0.0 B)
    RX errors 0  dropped 0  overruns 0  frame 0
    TX packets 0  bytes 0 (0.0 B)
    TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

此时,客户机里这个网卡是没有IP地址的,因为没有这个接口相对应的配置文件。

按照RHEL 7的使用手册,我们添加一个这样的配置文件,然后ifup这个接口即可。

[root@kvm-guest ~]# cat /etc/sysconfig/network-scripts/ifcfg-ens6
TYPE=Ethernet
BOOTPROTO=dhcp
DEFROUTE=yes
PEERDNS=yes
PEERROUTES=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_PEERDNS=yes
IPV6_PEERROUTES=yes
IPV6_FAILURE_FATAL=no
NAME=ens6
DEVICE=ens6
ONBOOT=yes

[root@kvm-guest ~]# ifup ens6

[root@kvm-guest ~]# ifconfig ens6
ens6: flags=4163  mtu 1500
    inet 192.168.100.85  netmask 255.255.252.0  broadcast 192.168.103.255
    inet6 fe80::e890:ddff:fe71:ada8  prefixlen 64  scopeid 0x20
    ether ea:90:dd:71:ad:a8  txqueuelen 1000  (Ethernet)
    RX packets 873  bytes 73154 (71.4 KiB)
    RX errors 0  dropped 0  overruns 0  frame 0
    TX packets 259  bytes 34141 (33.3 KiB)
    TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

[root@kvm-guest ~]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.100.1   0.0.0.0         UG    100    0        0 ens6
192.168.100.0   0.0.0.0         255.255.252.0   U     100    0        0 ens6
192.168.122.0   0.0.0.0         255.255.255.0   U     0      0        0 virbr0

[root@kvm-guest ~]# ping 192.168.100.1
PING 192.168.100.1 (192.168.100.1) 56(84) bytes of data.
64 bytes from 192.168.100.1: icmp_seq=1 ttl=255 time=0.517 ms
64 bytes from 192.168.100.1: icmp_seq=2 ttl=255 time=0.547 ms
^C
--- 192.168.100.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.517/0.532/0.547/0.015 ms

5.3. SR-IOV使用问题解析

在使用SR-IOV时,可能也会遇到各种小问题。这里根据笔者的经验来介绍一些可能会遇到的问题及其解决方法。

5.3.1. VF在客户机中MAC地址全为零

如果使用Linux 3.9版本作为宿主机的内核,在使用igb或ixgbe驱动的网卡(如:Intel 82576、I350、82599等)的VF做SR-IOV时,可能会在客户机中看到igbvf或ixgbevf网卡的MAC地址全为零(即:00:00:00:00:00:00),从而导致VF不能正常工作。

比如,在一个Linux客户机的dmesg命令的输出信息中,可能会看到如下的错误信息:

igbvf 0000:00:03.0: irq 26 for MSI/MSI-X
igbvf 0000:00:03.0: Invalid MAC Address: 00:00:00:00:00:00
igbvf: probe of 0000:00:03.0 failed with error -5

关于这个问题,笔者曾向Linux/KVM社区报过一个bug,其网页链接为:

https://bugzilla.kernel.org/show_bug.cgi?id=55421

这个问题的原因是,Linux 3.9的内核代码中的igb或ixgbe驱动程序在做SR-IOV时,会将VF的MAC地址设置为全是零,而不是像之前那样使用一个随机生成的MAC地址。它这样调整主要也是为了解决两个问题:

  • 一是随机的MAC地址对Linux内核中的设备管理器udev很不友好,多次使用VF可能会导致VF在客户机中的以太网络接口名称持续变化(如可能变为eth100);
  • 二是随机生成的MAC地址并不能完全保证其唯一性,有很小的概率可能与其他网卡的MAC地址重复而产生冲突。

对于VF的MAC地址全为零的问题,可以通过如下两种方法之一来解决。

1)在分配VF给客户机之前,在宿主机中用ip命令来设置需要使用的VF的MAC地址。命令行操作实例如下:

[root@kvm-host ~]# ip link set eno2 vf 0 mac 52:54:00:56:78:9a

在上面的命令中,eno2为宿主机中PF对应的以太网接口名称,0代表设置的VF是该PF的编号为0的VF(即第一个VF)。那么,如何确定这个VF编号对应的PCI-E设备的BDF编号呢?可以使用如下的两个命令来查看PF和VF的关系:

[root@kvm-host ~]# ethtool -i eno2
driver: igb
version: 5.3.0-k
firmware-version: 1.63, 0x80000c80
expansion-rom-version: 
bus-info: 0000:03:00.3
supports-statistics: yes
supports-test: yes
supports-eeprom-access: yes
supports-register-dump: yes
supports-priv-flags: no
[root@kvm-host ~]# ls -l /sys/bus/pci/devices/0000\:03\:00.3/virtfn*
lrwxrwxrwx 1 root root 0 Dec 25 19:04 /sys/bus/pci/devices/0000:03:00.3/virtfn0 -> ../0000:03:10.3
lrwxrwxrwx 1 root root 0 Dec 25 19:04 /sys/bus/pci/devices/0000:03:00.3/virtfn1 -> ../0000:03:10.7 

2)可以升级客户机系统的内核或VF驱动程序,比如可以将Linux客户机升级到使用Linux 3.9之后的内核及其对应的igbvf驱动程序。最新的igbvf驱动程序可以处理VF的MAC地址为零的情况。

5.3.2. Windows客户机中关于VF的驱动程序

对于Linux系统,宿主机中PF使用的驱动(如igb、ixgbe等)与客户机中VF使用的驱动(如igbvf、ixgbevf等)是不同的。当前流行的Linux发行版(如RHEL、Fedora、Ubuntu等)中都默认带有这些驱动模块。而对于Windows客户机系统,Intel网卡(如82576、82599等)的PF和VF驱动是同一个,只是分为32位和64位系统两个版本。有少数的最新Windows系统(如Windows 8、Windows 2012 Server等)默认带有这些网卡驱动,而多数的Windows系统(如Windows 7、Windows 2008 Server等)都没有默认带有相关的驱动,需要自行下载和安装。例如,前面提及的Intel网卡驱动都可以到其官方网站(http://downloadcenter.intel.com/Default.aspx)下载。

5.3.3. 少数网卡的VF在少数Windows客户机中不工作

读者在进行SR-IOV的实验时,可能遇到VF在某些Windows客户机中不工作的情况。笔者就遇到过这样的情况,在用默认的qemu-kvm命令行启动客户机后,Intel的82576、82599网卡的VF在32位Windows 2008 Server版的客户机中不能正常工作,而在64位客户机中的工作正常。该问题的原因不在于Intel的驱动程序,也不在于KVM中SR-IOV的代码逻辑不正确,而在于默认的CPU模型是qemu64,它不支持MSI-X这种中断方式,而32位的Windows 2008 Server版本中的82576、82599网卡的VF只能用MSI-X中断方式来工作。所以,需要在通过命令行启动客户机时指定QEMU模拟的CPU的类型(在5.2.4节中介绍过),从而可以绕过这个问题。可以用“-cpu SandyBridge”“-cpu Westmere”等参数来指定CPU类型,也可以用“-cpu host”参数来尽可能多地将物理CPU信息暴露给客户机,还可以用“-cpu qemu64,model=13”来改变qemu64类型CPU的默认模型。通过命令行启动一个32位Windows 2008 Server客户机,并分配一个VF给它使用,命令行操作如下:

[root@kvm-host ~]# qemu-system-x86_64 32bit-win2k8.img -smp 2 -cpu SandyBridge -m 1024 -device vfio-pci,host=06:10.1 -net none

在客户机中,网卡设备正常显示,网络连通状态正常,如图6-15所示。

另外,笔者也曾发现个别版本的VF(如:Intel I350)在个别版本的Windows(如:Windows 2008 Server)中不工作的情况,如下面这个bug所示,目前也没有被修复。

https://bugzilla.kernel.org/show_bug.cgi?id=56981

所以读者可以根据硬件和操作系统的实际兼容情况选择合适的配置来使用SR-IOV特性。

你可能感兴趣的:(2. 设备直接分配(VT-d))