在私有云桌面中,设备的透传(passthrough)与重定向(redirection)一直以来都是作为基本功能出现的。两者的在使用上的区别是前者一般将主机上的设备直接传递给在其中运行的虚拟机,后者则是将客户端的设备通过网络传递给其正在连接的虚拟机,相同点是当传递至虚拟机或虚拟机归还设备时,这对于主机来说是个设备热插拔操作。
在QEMU中,PCI/PCI-E设备目前仅支持透传(某些商业软件可对PCI/PCI-E设备进行重定向),且需要在主机BIOS设置中CPU打开Intel VT-d/选项(AMD CPU与之对应的是AMD Vi),可透传的设备包括显卡、声卡、HBA卡、网卡、USB控制器等,其中某些设备需要额外设置(比如IOMMU)才可进行透传。
使用libvirt透传PCI/PCI-E设备时需要知道要透传设备的总线地址,以在域定义中指定要透传的设备。一般落实到QEMU中有这些为透传准备的设备模型,包括pci-assgn、vfio-pci、vfio-vga等。
以透传主机网卡为例:
[root@node1 ~]# lspci
00:00.0 Host bridge: Intel Corporation 440BX/ZX/DX - 82443BX/ZX/DX Host bridge
...
02:05.0 Ethernet controller: Intel Corporation 82545EM Gigabit Ethernet Controller (Copper) (rev 01)
然后新建一个设备定义文件,在虚拟机运行时添加此设备,也可将其写入至虚拟机的域定义文件作为永久设备:
[root@node1 ~]# cat >> pci-e1000.xml<
EOF
[root@node1 ~]# virsh attach-device win7 pci-e1000.xml
如此便可将主机网卡透传至虚拟机中,如果使用vfio-pci需要加载vfio相关内核模块,具体可参考以下两节内容。
需要注意的是,不是所有的主机、虚拟机系统和PCI/PCI-E设备都支持热插拔,在不支持的系统中进行热插拔的话可能会造成虚拟机死机,甚至可能造成主机死机。
SR-IOV全称为Single Root I/O Virtualization, 是一种基于硬件的虚拟化解决方案,可提高 设备利用率,其功能实现最早在Linux系统中 。SR-IOV 标准允许在虚拟机之间 共享PCI-E 设备,并且它是在硬件中实现的, 虚拟设备可以获得与透传方式相当的 I/O 性能。
SR-IOV中引入了物理功能(Physical Function)与虚拟功能(Virtual Function)两个概念,其中物理功能是指物理设备拥有可配置的完整资源,虚拟功能则使得虚拟设备能够共享一部分物理资源以提供给虚拟机使用 。启用了 SR-IOV 并且具有适当的硬件和 设备驱动 支持的 PCI -E 设备 在系统中可 显示为多个 独立 的 虚拟 设备,每个都 拥 有自己的 I/O空间 。 目前使用最多的SR-IOV设备是万兆网卡,主要厂商有Intel、QLogic等。
笔者将以支持SR-IOV功能的Intel 82599网卡为例介绍SR-IOV的完整使用过程,其中会涉及到QEMU的vfio-pci透传设备模型以及设备IOMMU。
首先,我们需要修改主机启动引导参数以开启intel-iommu。此处读者可能会将intel-iommu与iommu混淆,前者控制的是基于Intel VT-d的IOMMU,它可以使系统进行设备的DMA地址重映射(DMAR)等多种高级操作为虚拟机使用做准备,且此项默认关闭,而后者主要控制是GART( Graphics Address Remapping Table ) IOMMU,目的是让有32位内存访问大小的设备可以进行DMAR操作,通常用于USB设备、声卡、集成显卡等,会在主机内存3GB以上的系统中默认开启。
# 修改/boot/grub2/grub.cfg,形式如下
linux16 /vmlinuz-3.10.0-327.3.1.el7.x86_64 root=UUID=ff78a51d-4759-464f-a1fd-2712a4943202 ro rhgb quiet LANG= zh_CN.UTF-8 intel_iommu=on
initrd16 /initramfs-3.10.0-327.3.1.el7.x86_64.im
然后重新加载网卡驱动模块,并设置模块中的最大VF数以使得设备虚拟出一定数量的网卡。不同厂商的网卡的驱动模块不同,其打开虚拟功能的参数也不同。另外,部分设备由于厂商策略原因,Linux内核自带的驱动不一定拥有VF相关设置,需要从官网单独下载并替换原有驱动。
# 查看网络设备总线地址,此款网卡拥有双万兆网口
[root@node3 ~]# lspci -nn | grep -i ethernet
04:00.0 Ethernet controller [0200]: Intel Corporation Ethernet 10G 2P X520 Adapter [8086:154d] (rev 01)
04:00.1 Ethernet controller [0200]: Intel Corporation Ethernet 10G 2P X520 Adapter [8086:154d] (rev 01)
# 查看设备驱动
[root@node3 ~]# lspci -s 04:00.0 -k
04:00.0 Ethernet controller: Intel Corporation Ethernet 10G 2P X520 Adapter (rev 01)
Subsystem: Intel Corporation 10GbE 2P X520 Adapter
Kernel driver in use: ixgbe
# 查看驱动参数
[root@node3 ~]# modinfo ixgbe
filename: /lib/modules/3.10.0-327.3.1.el7.x86_64/kernel/drivers/net/ethernet/intel/ixgbe/ixgbe.ko
version: 4.0.1-k-rh7.2
license: GPL
description: Intel(R) 10 Gigabit PCI Express Network Driver
author: Intel Corporation,
rhelversion: 7.2
srcversion: FFFD5E28DF8860A5E458CCB
alias: pci:v00008086d000015ADsv*sd*bc*sc*i*
...
alias: pci:v00008086d000010B6sv*sd*bc*sc*i*
depends: mdio,ptp,dca
intree: Y
vermagic: 3.10.0-327.3.1.el7.x86_64 SMP mod_unload modversions
signer: CentOS Linux kernel signing key
sig_key: 3D:4E:71:B0:42:9A:39:8B:8B:78:3B:6F:8B:ED:3B:AF:09:9E:E9:A7
sig_hashalgo: sha256
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)
# 重新加载内核,修改max_vfs为4,并将此参数写入/etc/modprobe.d/下的文件以便开机加载
[root@node3 ~]# modprobe -r ixgbe; modprobe ixgbe max_vfs=4
[root@node3 ~]# cat >> /etc/modprobe.d/ixgbe.conf<options ixgbe max_vfs=4
EOF
# 再次查看网络设备,可发现多了4个虚拟网卡,并且设备ID不同于物理网卡
[root@node3 ~]# lspci | grep -i ethernet
02:00.3 Ethernet controller [0200]: Broadcom Corporation NetXtreme BCM5719 Gigabit Ethernet PCIe [14e4:1657] (rev 01)
04:00.0 Ethernet controller [0200]: Intel Corporation Ethernet 10G 2P X520 Adapter [8086:154d] (rev 01)
04:00.1 Ethernet controller [0200]: Intel Corporation Ethernet 10G 2P X520 Adapter [8086:154d] (rev 01)
04:10.0 Ethernet controller [0200]: Intel Corporation 82599 Ethernet Controller Virtual Function [8086:10ed] (rev 01)
04:10.1 Ethernet controller [0200]: Intel Corporation 82599 Ethernet Controller Virtual Function [8086:10ed] (rev 01)
04:10.2 Ethernet controller [0200]: Intel Corporation 82599 Ethernet Controller Virtual Function [8086:10ed] (rev 01)
04:10.3 Ethernet controller [0200]: Intel Corporation 82599 Ethernet Controller Virtual Function [8086:10ed] (rev 01)
虚拟网卡被主机发现以后,我们需要额外加载vfio-pci以及vfio-iommu-type1两个模块,然后将虚拟网卡与原驱动解绑并重新绑定至vfio-pci驱动。其中vfio-pci驱动是专门为现在支持DMAR和中断地址重映射的PCI设备开发的驱动模块,它依赖于VFIO驱动框架,并且借助于vfio-iommu-type1模块实现IOMMU的重用:
# 加载vfio-pci
[root@node3 ~]# modprobe vfio-pci
# 加载vfio-iommu-type1以允许中断地址重映射,如果主机的主板不支持中断重映射功能则需要指定参数“allow_unsafe_interrupt=1”
[root@node3 ~]# modprobe vfio-iommu-type1 allow_unsafe_interrupt=1
# 将虚拟网卡与原驱动解绑
[root@node3 ~]# echo 0000:04:10.0 > /sys/bus/pci/devices/0000\:04\:10.0/ driver/unbind
[root@node3 ~]# echo 0000:04:10.1 > /sys/bus/pci/devices/0000\:04\:10.1/ driver/unbind
[root@node3 ~]# echo 0000:04:10.2 > /sys/bus/pci/devices/0000\:04\:10.2/ driver/unbind
[root@node3 ~]# echo 0000:04:10.3 > /sys/bus/pci/devices/0000\:04\:10.3/ driver/unbind
# 将虚拟网卡按照设备ID全部与vfio-pci驱动绑定
[root@node3 ~]# echo 8086 10ed > /sys/bus/pci/drivers/vfio-pci/new_id
# 查看虚拟设备现在使用的驱动
[root@node3 ~]# lspci -k -s 04:10.0
04:10.0 Ethernet controller: Intel Corporation 82599 Ethernet Controller Virtual Function (rev 01)
Subsystem: Intel Corporation Device 7b11
Kernel driver in use: vfio-pci
然后我们即可在虚拟机中使用这些虚拟网卡,需要在QEMU命令行中添加设备选项,形似“ -device vfio-pci,host=04:10. 0 ,id=hostdev0,bus=pci.0,multifunction=on,addr=0x9 ”,对应的libvirt定义如下:
如果使用vfio-pci透传PCI-E设备,我们需要使用QEMU机器模型Q35,并添加相应的PCI-E总线参数,除此之外,设备驱动的解绑与绑定操作可以简化为如下所示的脚本操作:
[root@node3 ~]# cat vfio-bind.sh
#!/bin/bash
modprobe vfio-pci
for var in "$@"; do
for dev in $(ls /sys/bus/pci/devices/$var/iommu_group/devices); do
vendor=$(cat /sys/bus/pci/devices/$dev/vendor)
device=$(cat /sys/bus/pci/devices/$dev/device)
if [ -e /sys/bus/pci/devices/$dev/driver ]; then
echo $dev > /sys/bus/pci/devices/$dev/driver/unbind
fi
echo $vendor $device > /sys/bus/pci/drivers/vfio-pci/new_id
done
done
USB包括控制器和外设,控制器位于主机上且一个主机可同时拥有多个USB控制器,控制器通过root hub提供接口供其他USB设备连接,而这些USB设备又可以分为hub、存储、智能卡、加密狗、打印机等。目前常用的USB协议有1.1、2.0、3.0、3.1(Type-C)等。
在QEMU中,我们一般可以对USB控制器进行透传,外设进行透传或重定向。
首先USB控制器也位于PCI总线上,所以我们将整个控制器及其上面的hub、外设全部透传至虚拟机中,可以参考上一节中的相关域定义,不同的是我们需要找到USB控制器对应的PCI总线地址,如下所示:
[root@node4 ~]# lspci -nn| grep -i usb
00:1a.0 USB controller [0c03]: Intel Corporation C610/X99 series chipset USB Enhanced Host Controller #2 [8086:8d2d] (rev 05)
00:1d.0 USB controller [0c03]: Intel Corporation C610/X99 series chipset USB Enhanced Host Controller #1 [8086:8d26] (rev 05)
然后选择要透传的USB控制器,我们需要查看主机线路简图或外设简图以确定要透传的USB接口,如果是对主机直接操作需避免将连有USB键盘鼠标设备的控制器透传至虚拟机,否则会造成后续操作的不便。
[root@node4 ~]# lsusb
Bus 001 Device 002: ID 8087:800a Intel Corp.
Bus 002 Device 002: ID 8087:8002 Intel Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 003: ID 12d1:0003 Huawei Technologies Co., Ltd.
参考上一节中的驱动绑定,使用vfio-pci对USB控制器进行透传,假设要透传的控制器为2号控制器,它的总线地址为00:1a.0,设备ID为8086:8d26:
[root@node3 ~]# echo 0000:00:1a.0 > /sys/bus/pci/devices/0000\:04\:10.0/ driver/unbind
[root@node3 ~]# echo 8086 8d26 > /sys/bus/pci/drivers/vfio-pci/new_id
最后在QEMU中添加参数形如“ -device vfio-pci,host=0 0 :1 a . 0 ,id=hostdev0,bus=pci.0, multifunction=on,addr=0x9 ”即可。
QEMU下USB外设的透传相对比较容易,只需要在域定义中添加对应的USB外设的厂商与设备ID即可,以透传USB-Key为例:
# 查看设备总线地址与ID
[root@node1 ~]# lsusb
Bus 001 Device 002: ID 8087:800a Intel Corp.
Bus 002 Device 002: ID 8087:8002 Intel Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 003: ID 04b9:8001 Rainbow Technologies, Inc.
Bus 002 Device 005: ID 096e:0202 Feitian Technologies, Inc.
Bus 002 Device 004: ID 12d1:0003 Huawei Technologies Co., Ltd.
# 然后在域定义中添加设备ID,也可指定设备的总线地址
对应到QEMU的参数即是“ -device usb-host,vendorid=0x 04b9 ,productid=0x 8 001 ”或者“ -device usb-host,hostbus=1,hostaddr=3,id=hostdev0 ”,也可是“-usbdevice host:0529:0001”等形式。
USB外设的重定向是私有桌面云的必备功能之一,目前其实现方法包括硬件和软件两种,其中软件实现最为常用的是USB over TCP/IP,即通过TCP/IP协议重定向客户端USB外设到虚拟机中。QEMU桌面协议Spice里的对应实现是Spice USB Redirection,即通过添加一个专用的channel用于客户端到虚拟机的USB重定向。
USB over TCP/IP的一般实现如图9-1所示,其中PDD为具体的USB外设驱动(Peripheral Device Driver),HCD为USB控制器驱动(Host Controller Driver),Stub Driver为客户端USB外设的统一驱动,VHCI Driver为虚拟机中的USB控制器驱动。当客户端插入USB外设时,系统会对其使用Stub驱动,设备重定向后USB请求与数据会被Stub驱动封装,经由TCP/IP传递至虚拟机的VHCI驱动,反过来亦如此。
图9-1 USB over IP原理
接下来笔者使用usbredir-server工具,将Ubuntu客户端的U盘重定向至另一主机中的虚拟机。
首先在Ubuntu中确定U盘的设备ID,安装usbredir-server并在44444端口监听:
root@ubuntu:~# apt-get install usbredir-server
root@ubuntu:~# lsusb
Bus 001 Device 004: ID 0781:5567 SanDisk Corp. Cruzer Blade
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 003: ID 0e0f:0002 VMware, Inc. Virtual USB Hub
Bus 002 Device 002: ID 0e0f:0003 VMware, Inc. Virtual Mouse
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
# 这里可以将USB设备的总线地址或者设备ID作为参数
root@ubuntu:~# usbredirserver -p 44444 -v 4 0781:5567
然后在另一台主机中启动虚拟机,并添加如下设备定义:
虚拟机启动后,可以看到对应的QEMU命令中多了一个特殊的usb-redir设备,形似“ -chardev socket,id=charredir2,host=192.168.0.58,port= 44444 -device usb-redir,chardev=charredir2,id=redir2 ”。这个设备即是QEMU的USB重定向设备后端,它可用在USB over TCP/IP中,也可作为Spice USB channel的设备后端。
需要注意的是,上述实现中 hypervisor会主动发送请求到客户端端口(Spice USB Redirection无此要求) ,而这在实际场景中往往比较难以实现。采用这种实现的私有云厂平台会在特定服务器(也可以是hypervisor)中启用代理网关或者网络隧道,客户端将网络端口映射到其上的某个端口,Hypervisor再与服务器连接,从而完成双方间接通信,技术细节可以参考Spice协议的squid代理实现。
1.1.4 串口与 并口
QEMU 中串口与并口设备一般都可进行透传与重定向操作,其中透传比较简单,只需要将本地串口 / 并口的设备节点当做设备后端即可,形如“ -parallel /dev/lp0 ”,而重定向的思路与 USB over TCP/IP 较为类似。
重定向时我们需要使用工具将客户端串口 / 并口设备的输入 / 输出暴露到客户端的网络端口, hypervisor 再将客户端的 IP 地址与 TCP 端口作为虚拟机的串口 / 并口设备后端参数进行连接,如图 9-2 所示。
Linux客户端中可以使用ser2net作为串口/并口的服务端,Windows客户端中也有对应的实现,笔者以Linux中的ser2net为例,介绍QEMU中的串口与并口重定向。
首先在客户端启动监听服务:
# 本地串口为ttyS1,并口打印机为lp0(非parport0)
root@ubuntu:~# apt-get install ser2net
root@ubuntu:~# ser2net -C 44444:raw:0:/dev/ttyS1
root@ubuntu:~# ser2net -C 44445:rawlp:0:/dev/lp0
然后在服务端添加串口/并口设备,QEMU命令行如下所示:
root@ubuntu:~# qemu-kvm -m 2G -smp 2,sockets=1 \
-device isa-serial,chardev=serial0 \
-chardev socket,id=serial0,host=192.168.0.40,port=44444 \
-device isa-parallel,chardev=lp0 \
-chardev socket,id=lp0,host=192.168.0.40,port=44445
和USB over TCP/IP相同,在实际场景中可能需要代理网关或网络隧道。
本文节选自《KVM私有云架构设计与实践》第九章。
作者介绍: 云技术社区专家 蒋迪
蒋迪,资深虚拟化基础设施工程师,《KVM私有云架构设计与实践》作者,云技术社区专家,擅长KVM云平台架构解析与虚拟化POC,具有一线开发与交付经验。
《KVM私有云架构设计与实践》已在各大图书商场有售。
点击原文链接 进去蒋老师云技术社区千聊直播回放页面,收看蒋老师《 OpenStack高可用集群案例实战 》 干货分分享。