Gentoo PCI KVM设备直通
这篇是一篇如何在Gentoo下面做KVM直通的文章,取自之前的笔记中。
最近手里面的电脑送的差不多了,现在手边只有一个Mac 和一个工作站了。平时那个Mac太重了也不太想要背(画画也不是很舒服,画画的时候噪音挺大的),看了手边的工作站pcie槽位还挺多cpu的通道数也足够,于是打算折腾一下KVM PCI设备直通,通过PCI设备直通将一张显卡分配给虚拟机还有音频USB之类的周边设备从而达到想要的效果(画画摸鱼机!)。
环境说明
这里先说明一下环境以及如何分配这些资源。 我的工作站是一个非常年迈的ThinkStation S20 ,配置如下
型号 | CPU | 内存 | 硬盘 | 显卡 | USB扩展卡 | 声卡 |
---|---|---|---|---|---|---|
ThinkStation S20 | X5670 | 8G * 3 | PCIE SSD 800G 500G * 1 12TB *2 | GT 620 Quadro K2000 | 不知名的PCI 2.0 扩展卡 | SB CA10300 和板载声卡 |
以上的硬件是符合这次的PCI设备直通的基本要求。如果你也想要尝试做PCI设备直通请确保你的硬件满足以下特性。
- CPU 支持VT-d (intel) amd-vi(amd)
- 两张显卡(如果你的CPU带有核心显卡同时主板支持核心显卡输出那么你只需要一张就足够了,如果不支持就需要2张独立显卡)
- iommu支持 (iommu负责把主板上的设备分组,我们等下就是要在宿主机上遮盖掉要给虚拟机分配设备的组)
这次打算是创建一个Windows虚拟机将K2000和SB声卡以及PCI 2.0的扩展卡直通到这个虚拟机,作为日常画画用的节点。(或者是后续换成Mac虚拟机)。 磁盘分配是打算分配200G的SSD空间给这个虚拟机其余以网络磁盘方式分配给虚拟机。
环境准备
这里准备一下创建虚拟机的准备比如说KVM环境安装镜像 etc. 宿主机系统我这里选择的是Gentoo
部署KVM
首先运行这条命令查看你的机器是否支持虚拟化
grep --color -E "vmx|svm" /proc/cpuinfo
如果没有类似以下的输出就需要检查一下BIOS中虚拟化的选项是否开启,或者是查看 Intel Ark 来查找你的CPU是否支持虚拟化。
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 popcnt aes lahf_lm epb pti tpr_shadow vnmi flexpriority ept vpid dtherm ida arat
确保你的内核以下选项打开
[*] Virtualization --->
<*> Kernel-based Virtual Machine (KVM) support
如果你的是Intel 处理器请确保将以下选项编译为模块或者是包含在内核中
[*] Virtualization --->
KVM for Intel processors support
如果你的是AMD 处理器请确保将以下选项编译为模块或者是包含在内核中
[*] Virtualization --->
KVM for AMD processors support
我们这里在构建qemu的时候还需要开启 vhost-net的USE这里推荐打开以下内核选项
[*] Virtualization --->
<*> Host kernel accelerator for virtio net
对于高级网络的支持
Device Drivers --->
[*] Network device support --->
[*] Network core driver support
<*> Universal TUN/TAP device driver support
这里打算使用的是桥接网络,所以还需要打开以下选项
[*] Networking support --->
Networking options --->
<*> The IPv6 protocol
<*> 802.1d Ethernet Bridging
更改好这些内核选项后记得更新内核,再使用新的内核启动。
make -j12 && make modules_install && make install && grub-mkconfig -o /boot/grub/grub.cfg && reboot
重启后,现在我们可以安装QEMU了,这里安装有两种不同的方式(添加我们需要的USE)。
USE="spice usb usbredir vhost-net" emerge qemu
或者是将这些USE写到一个文件中。
创建并编辑 /etc/portage/package.use/qemu
,内容如下
app-emulation/qemu spice usb usbredir vhost-net
然后安装QEMU
emerge qemu
接下来我们需要部署一个管理虚拟机的工具包(libvirt)
emerge app-emulation/libvirt
启动 libvirt 并加入开启启动
/etc/init.d/libvirtd start
rc-update add libvirt default
网桥设置,我这里虚拟机需要和内网的其他服务器进行互通,所以这里选择的是桥接网络为了方便管理桥接网络这里还需要一个工具
emerge bridge-utils
同时为了让虚拟机支持UEFI这里还需要安装一个包用于支持虚拟机UEFI启动
emerge sys-firmware/edk2-ovmf
如果直接安装windows可能会找不到磁盘还需要一个包
emerge app-emulation/virtio-win
如果想要一个gui管理界面可以安装virt-manager:
emerge virt-manager
配置libvirt
为了能够安装有uefi支持的虚拟机还需要做一些配置。
编辑/etc/libvirt/qemu.conf
添加以下内容:
nvram = [
"/usr/share/edk2-ovmf/OVMF_CODE.fd:/usr/share/edk2-ovmf/OVMF_VARS.fd",
"/usr/share/edk2-ovmf/OVMF_VARS.secboot.fd:/usr/share/edk2-ovmf/OVMF_VARS.fd"
]
修改/usr/share/qemu/firmware/50-edk2-x86_64-secure.json
内容如下
{
"description": "UEFI firmware for x86_64, with Secure Boot and SMM",
"interface-types": [
"uefi"
],
"mapping": {
"device": "flash",
"executable": {
"filename": "/usr/share/edk2-ovmf/OVMF_CODE.fd",
"format": "raw"
},
"nvram-template": {
"filename": "/usr/share/edk2-ovmf/OVMF_VARS.fd",
"format": "raw"
}
},
"targets": [
{
"architecture": "x86_64",
"machines": [
"pc-q35-*"
]
}
],
"features": [
"acpi-s3",
"amd-sev",
"requires-smm",
"secure-boot",
"verbose-dynamic"
],
"tags": [
]
}
重启libvirtd
/etc/init.d/libvirtd restart
配置桥接网络
配置网络有两种方式一种是用NetworkMnager去接管另一种是直接配置文件创建一个桥接网络,这里分别说明以下两种的配置方法。
- 配置文件的方法
编辑 /etc/conf.d/net
内容如下
bridge_br0="enp6s0"
# 这个enp6s0 是我的物理网卡替换成你的
# Bridge static config
config_br0="10.0.0.10 netmask 255.255.255.0"
routes_br0="default via 10.0.0.1"
bridge_forward_delay_br0=0
bridge_hello_time_br0=1000
bridge_stp_state_br0=1
创建一个软连接
ln -s /etc/init.d/net.lo /etc/init.d/net.br0
启动这个桥接网卡
rc-service net.br0 start
加入开机启动
rc-update add net.br0 default
验证
brctl show
bridge name bridge id STP enabled interfaces
br0 8000.70f39503c843 yes enp6s0
这样桥接网卡就配置好了
- NetworkManager 首先创建一个网桥设备
nmcli con add type bridge ifname br0
添加一个bridge-slave的网卡
nmcli con add type bridge-slave ifname enp6s0 master br0
配置stp(防止桥回路)
nmcli con modify br0 bridge.stp yes
给br0 配置ip
nmcli con mod br0 ipv4.method manual ipv4.addresses "10.0.0.10/24" ipv4.gateway 10.0.0.1 ipv4.dns 114.114.114.114 connection.autoconnect yes
nmcli con up br0
验证
brctl show
bridge name bridge id STP enabled interfaces
br0 8000.70f39503c843 yes enp6s0
在宿主启动iommu
在宿主机器启动iommu需要给内核一些启动参数就可以启用了
编辑 /etc/default/grub
找到 GRUB_CMDLINE_LINUX=
修改内容如下
GRUB_CMDLINE_LINUX="iommu=pt intel_iommu=on pcie_acs_override=downstream,multifunction vfio_iommu_type1.allow_unsafe_interrupts=1
然后更新grub 并重启
grub-mkconfig -o /boot/grub/grub.cfg&& reboot
开机后使用dmegs查看ring buffer中的信息
dmesg | grep 'IOMMU enabled'
[ 0.251621] DMAR: IOMMU enabled
这里可以看到IOMMU已经启用了
在宿主机系统中预留设备
为了能够在宿主机上预留设备我们需要再对内核进行修改,这次用到的是VFIO(可以将设备IO,DMA给到用户态)
Device Drivers --->
<*> VFIO Non-Privileged userpsace driver framework --->
[*] VFIO No-IOMMU support ----
<*> VFIO support for PCI devices
[*] VFIO PCI support for VGA devices
< > Mediated device driver framework
这里的VFIO support for PCI devices看自己喜好去编译我这里直接内建在内核了,然后通过grub传参数。 然后构建新的内核
make -j12 && make modules_install && make install && grub-mkconfig -o /boot/grub/grub.cfg && reboot
重启后我们来找我们需要的设备 这里用一个脚本来快捷的找这些设备
#!/bin/bash
shopt -s nullglob
for g in /sys/kernel/iommu_groups/*; do
echo "IOMMU Group ${g##*/}:"
for d in $g/devices/*; do
echo -e "\t$(lspci -nns ${d##*/})"
done;
done;
保存到文件并运行,我们从输出中找到需要的信息并记录下来 这里是一个声卡的组可以看到这个声卡是单独放在一组的我们记住最后的两段数字和字母的组合8086:3a3e
IOMMU Group 16:
00:1b.0 Audio device [0403]: Intel Corporation 82801JI (ICH10 Family) HD Audio Controller [8086:3a3e]
接着往后看看到
IOMMU Group 17:
00:1c.0 PCI bridge [0604]: Intel Corporation 82801JI (ICH10 Family) PCI Express Root Port 1 [8086:3a40]
00:1c.4 PCI bridge [0604]: Intel Corporation 82801JI (ICH10 Family) PCI Express Root Port 5 [8086:3a48]
04:00.0 PCI bridge [0604]: Tundra Semiconductor Corp. Tsi381 PCIe to PCI Bridge [10e3:8111] (rev 02)
05:00.0 Multimedia audio controller [0401]: Creative Labs CA0108/CA10300 [Sound Blaster Audigy Series] [1102:0008]
06:00.0 Ethernet controller [0200]: Broadcom Inc. and subsidiaries NetXtreme BCM5755 Gigabit Ethernet PCI Express [14e4:16
7b] (rev 02)
这是我们最不想遇到的一种情况因为我额外添加的声卡是和网卡以及其他的pcie 端口是在一个组里面的好在是板载的声卡是独立的 接下来是我们的额外的usb卡的信息
IOMMU Group 19:
00:1e.0 PCI bridge [0604]: Intel Corporation 82801 PCI Bridge [8086:244e] (rev 90)
07:0d.0 USB controller [0c03]: NEC Corporation OHCI USB Controller [1033:0035] (rev 43)
07:0d.1 USB controller [0c03]: NEC Corporation OHCI USB Controller [1033:0035] (rev 43)
07:0d.2 USB controller [0c03]: NEC Corporation uPD72010x USB 2.0 Controller [1033:00e0] (rev 04)
还有最为关键的显卡的信息
IOMMU Group 21:
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GK107GL [Quadro K2000] [10de:0ffe] (rev a1)
01:00.1 Audio device [0403]: NVIDIA Corporation GK107 HDMI Audio Controller [10de:0e1b] (rev a1)
IOMMU Group 22:
02:00.0 VGA compatible controller [0300]: NVIDIA Corporation GF119 [GeForce GT 620 OEM] [10de:1049] (rev a1)
02:00.1 Audio device [0403]: NVIDIA Corporation GF119 HDMI Audio Controller [10de:0e08] (rev a1)
修改grub配置文件在我们的 GRUB_CMDLINE_LINUX
这行追加
vfio-pci.ids=8086:3a3e,8086:244e,1033:0035,1033:0035,1033:00e0,10de:0ffe,10de:0e1b
更新grub并重启
grub-mkconfig -o /boot/grub/grub.cfg
如果你的第二张显卡是插着显示器的你就可以看到引导到遮盖的pci设备的时候会卡在那里但是另外一个显示器是引导到了系统。
部署虚拟机
在这个章节我们将会部署一台windows10的虚拟机。
准备条件
- Windows 10 镜像
- Quadro 显卡驱动
准备好这些之后就可以开始安装了,首先创建一个虚拟机磁盘:
qemu-img create -f qcow2 /etc/libvirt/storage/windows10.qcow2 200G
如果你开了selinux不要忘记改一下selinux的布尔值:
setsebool -P virt_use_vfio=on
setsebool -P virt_use_sysfs=on
安装windows10虚拟机
sudo virt-install --virt-type=kvm \
--name=windows10 \
--memory=8196 \
--vcpus=6 \
--boot uefi \
--machine q35 \
--cpu=host-passthrough \
--file=/etc/libvirt/storage/windows10.qcow2 \
--cdrom=/etc/libvirt/storage/iso/windows10.iso \
--os-variant win10 \
--network bridge=br0,model=e1000 \
--graphics=vnc,password=passwd,listen=0.0.0.0,port=5900
然后使用vnc连接到这台虚拟机进行安装
安装windows的步骤我就不一一赘述。
添加PCI设备
现在离成功只有一步添加PCI设备了,在关闭虚拟机之后添加对应的pci设备。
通过 nodedev-list 查看宿主机的设备。
virsh nodedev-list --tree |grep pci
从中找到和我们设备一一对应的地址记下来
+- pci_0000_00_01_0
| +- pci_0000_01_00_0
| +- pci_0000_01_00_1
比如说这个地址是我们的显卡地址我们就需要记住这2个地址分配的时候要一起分配到虚拟机里面
pci_0000_01_00_0
pci_0000_01_00_1
我们可以用virsh 的 nodedev-dumpxml 子命令dump出来一份xml
virsh nodedev-dumpxml pci_0000_01_00_0
从输出中摘出需要记住的部分
bus='1'
slot='0'
function='0'
编辑虚拟机文件
virsh edit windows10
添加例子如 每个都需需要如上述这样添加 然后启动windows虚拟机
virsh start windows10
再使用vnc连接到虚拟机在系统里面将N卡驱动安装好,然后关机。 备份一份xml文件然后将vnc的删除。 最终的文件可以参考我的例子 最后来一个任务管理器的截图