QEMU/KVM启动物理分区的Windows并调优
1 启动物理分区的Windows系统
1.1 需求概述
先来说说我笔记本的硬盘配置,我通常装两块硬盘,一块安装Windows,另一块安装Linux。平常几乎只使用Linux,Window常年不开机,容易造成一块盘使用过度而另一块盘闲置的现象。且在这个微软几近垄断操作系统的时代,想要安安静静用Linux完成所有事情是不现实的,不提微软Office全家桶,哪怕是一众国产软件对Linux接近于0的支持也让无数用户直摇头。想解决这个问题,虚拟机(Virtual Machine, VM)运行Windows是一个非常好的方案。在Linux中运行Windows VM,其虚拟硬盘是文件模拟的,相当于两个系统同时损耗一块硬盘,对硬盘性能和寿命而言无疑是雪上加霜。针对这个问题,一个显而易见的解决方案是将Windows所在硬盘利用起来,但Linux对NTFS分区的支持毕竟不是原生,抛开性能不谈,不少文件系统间的特性也无法兼容,使用中总会影响体验。那如果能够利用QEMU/KVM启动物理硬盘(分区)上的Windows系统,让Windows系统作为VM直接访问硬盘,不经过Linux Host,岂不是可以均衡两块硬盘之间的利用率,减轻Linux系统盘的负担并提升性能?同时如果利用Virtio-SCSI驱动优化硬盘,更是可以达到提升VM硬盘性能的效果!实验是检验真理的唯一途径,通过查询资料外加本人亲自试验,最终取得成功。在此写下笔记,供需要时参考查阅。
1.2 原理
首先声明本人物理硬盘上安装的Windows系统版本是Win10。要想通过QEMU/KVM启动物理分区上的Win10,首先需要定位到目标分区,然后需要为目标分区创建对应的启动分区。现代启动分区是EFI分区,通过Windows ISO镜像即可创建。通过fdisk
命令可以列出Win10所在硬盘分区布局:
$ sudo fdisk -l /dev/nvme1n1
Disk /dev/nvme1n1: 953.87 GiB, 1024209543168 bytes, 2000409264 sectors
Disk model: UMIS RPEYJ1T24MKN2QWY
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 5FDAE92B-259B-4AB8-8D55-DE3A1EE8D99E
Device Start End Sectors Size Type
/dev/nvme1n1p1 2048 34815 32768 16M Microsoft reserved
/dev/nvme1n1p2 34816 268470271 268435456 128G Microsoft basic data
/dev/nvme1n1p3 268470272 2000408575 1731938304 825.9G Microsoft basic data
其中/dev/nvme1n1p2
为Win10系统所在分区(C:),/dev/nvme1n1p3
为Win10的扩展分区(D:)。直接将/dev/nvme1n1p2
穿透到VM作为系统盘无法启动系统,因为缺少系统启动依赖的必要元数据,这些元数据存储在EFI分区。我们要做的是使用mdadm
命令创建一个线性阵列,将/dev/nvme1n1p2
作为线性阵列中的一个分区,并使用其中另一个分区作为EFI分区引导该分区启动。根据GPT分区表规范,最终要创建的线性阵列分区布局如下:
接着将配置好的线性阵列作为一个整体供应给VM,作为其启动盘即可启动物理分区中的Win10系统。
第1节主要参考资料:
1.3 创建线性阵列
首先,创建两个文件,大小分别为100MB和1MB,用于后续挂载为文件Loop设备,作为线性阵列的一部分:
sudo mkdir -p /etc/libvirt/hooks/qemu.d/win10/md0 # 创建目标路径
cd /etc/libvirt/hooks/qemu.d/win10/md0
dd if=/dev/zero of=loop-efi0 bs=1M count=100
dd if=/dev/zero of=loop-efi1 bs=1M count=1
创建启动线性阵列脚本start-md0.sh
:
#!/usr/bin/env bash
WIN_PART=/dev/nvme1n1p2
EFI_DIR="/etc/libvirt/hooks/qemu.d/win10/md0"
if [[ -e /dev/md0 ]]; then
echo "/dev/md0 already exists" > /dev/kmsg 2>&1
exit 1
fi
if mountpoint -q -- "${WIN_PART}"; then
echo "Unmounting ${WIN_PART}..." > /dev/kmsg 2>&1
umount ${WIN_PART}
fi
modprobe loop
modprobe linear
LOOP0=$(losetup -f "${EFI_DIR}/loop-efi0" --show)
LOOP1=$(losetup -f "${EFI_DIR}/loop-efi1" --show)
mdadm --build --verbose /dev/md0 --chunk=512 --level=linear --raid-devices=3 ${LOOP0} ${WIN_PART} ${LOOP1}
chown $USER:disk /dev/md0
echo "$LOOP0 $LOOP1" > "${EFI_DIR}/.win10-loop-devices"
与之对应的停止线性阵列脚本stop-md0.sh
:
#!/usr/bin/env bash
EFI_DIR="/etc/libvirt/hooks/qemu.d/win10/md0"
mdadm --stop /dev/md0
xargs losetup -d < "${EFI_DIR}/.win10-loop-devices"
为脚本授予可执行权限,并以sudo运行start-md0.sh
脚本,此时操作系统将多出一个新的块设备/dev/md0
,这就是我们创建的线性阵列。接下来为/dev/md0
创建GPT分区表,并进行分区:
sudo parted /dev/md0
(parted) unit s
(parted) mktable gpt
(parted) mkpart primary fat32 2048 204799 # 取决于loop-efi0文件大小
(parted) mkpart primary ntfs 204800 268640255 # 取决于Win10物理分区扇区数
(parted) set 1 boot on
(parted) set 1 esp on
(parted) set 2 msftdata on
(parted) name 1 EFI
(parted) name 2 Windows
(parted) quit
注意分区时为Win10分区指定的结束扇区是268435456+204800-1=268640255,具体数值需要根据Win10物理分区扇区数计算。然后为EFI分区进行格式化:
sudo mkfs.msdos -F 32 -n EFI /dev/md0p1
操作完成后,/dev/md0
的分区布局如下:
$ sudo fdisk -l /dev/md0
Disk /dev/md0: 128.1 GiB, 137544859648 bytes, 268642304 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 126C8DF4-4BE7-4DC3-80C3-B47DAE679207
Device Start End Sectors Size Type
/dev/md0p1 2048 204799 202752 99M EFI System
/dev/md0p2 204800 268640255 268435456 128G Microsoft basic data
1.4 写入EFI分区
推荐使用virt-manager进行接下来的操作,virt-manager使用的具体操作过程不进行详细描述。在virt-manager中配置一个Win10 VM,配置要点如下:
- 芯片组Q35,固件UEFI;
- 使用Windows ISO作为第一启动项;
- 使用
/dev/md0
作为VM硬盘,类型设定为SATA。
配置完成后启动VM,进入ISO的系统安装流程,一直点击下一步,直到最后确认安装方式时选择“自定义”,随后进入分区界面。通过快捷键Shift+F10
调出CMD,输入如下指令:
diskpart
DISKPART> list disk
DISKPART> select disk 0 # 选择/dev/md0在VM中对应的硬盘
DISKPART> list volume # 在分区列表中记下EFI分区序号
DISKPART> select volume 2 # 选择EFI分区
DISKPART> assign letter=B # 为EFI分区分配驱动器号(B:)
DISKPART> exit
最后将驱动器C:中的系统启动信息写入到驱动器B:(EFI分区):
bcdboot C:\Windows /s B: /f ALL
注意这里的驱动器驱动器C:指代线性阵列中Windows安装的分区,不清楚是否可能会被分配为其他驱动器号,使用命令时建议核实好分区和驱动器号的对应关系。正常情况下该命令总是能执行成功,执行失败则说明创建线性阵列/dev/md0
时分区操作出现问题,通常是第二个分区(Win10物理分区)的结束扇区参数不匹配所致,请核对1.3节操作以纠正问题。EFI分区启动信息写入成功后关闭VM,将第一启动项改为/dev/md0
对应的硬盘,即可启动物理分区的Win10系统。
1.5 SMB文件共享
现在已经成功启动了物理分区的Win10系统,现在还需要解决Host和VM交换文件数据的问题。鉴于我们使用的是物理分区作为VM的硬盘,其文件系统是Windows原生支持的NTFS。为保持最大兼容性,将使用SMB将某个路径共享给Host,而不通过Host挂载NTFS分区再利用VioFS映射到VM的形式进行共享。
关于SMB不过多赘述,直接参考How to Mount Windows Share on Linux via CIFS即可。值得一提的是,若想以非root用户身份挂载SMB路径,则需在/etc/fstab
中写入对应的条目,如:
# win10 smb vmshare
//192.168.141.77/D /mnt/win10-extorage cifs credentials=/etc/smb-credentials/win10-vmshare,rw,suid,dev,exec,noauto,user,async 0 0
其中user
参数表示任意用户均可挂载,noauto
参数表示系统启动时不进行自动挂载。配置完成后,用以下命令进行挂载(无需使用sudo):
mount /mnt/win10-extorage
2 VM性能调优
2.1 使用Virtio-SCSI驱动硬盘
此前我们使用SATA驱动硬盘启动,为提升硬盘性能,建议将其切换为Virtio-SCSI。首先需要在VM的xml文件中添加一个virtio-scsi controller并删除其余scsi controller:
<controller type="scsi" model="virtio-scsi"/>
然后启动VM(此时硬盘还是SATA驱动),以管理员身份运行powershell,执行以下命令:
bcdedit /set "{current}" safeboot minimal
之后为VM安装virtio-win驱动,并关闭VM。再次启动VM将会进入安全模式,该模式下将自动装载所有驱动,包括virtio。后续正常启动将会自动装载并使用virtio驱动,安全模式下使用powershell关闭安全启动即可:
bcdedit /deletevalue "{current}" safeboot
关闭VM,修改xml配置,将硬盘改为Virtio-SCSI驱动:
<disk>
...
<target bus="scsi"/>
...
</disk>
再次启动VM即可。注意启动后应将Windows的“磁盘碎片整理服务”关闭,Virtio-SCSI似乎会使这个服务一直启动,导致极高的内存和CPU占用。实际上在固态硬盘上进行碎片整理没有必要,会极大地影响使用寿命。而如果是机械硬盘,则根本不需要Virtio-SCSI调优...
2.2 CPU Pin
CPU pin目的是将QEMU/KVM相关进程绑定到某些特定的核心运行,减少其在不同CPU核心上的切换开销,提升VM性能。在进行CPU pin之前,先使用lstopo
命令查看CPU拓扑:
sudo apt install hwloc # 安装相应软件包
lstopo --no-io
CPU pin的原则是保持VM和Host拥有相同的CPU配置,若将VM配置为支持超线程特性,则从第0个vCPU开始,每两个vCPU视作为一个core,如:(0, 1)、(2, 3)。超线程的VM CPU配置也应和Host一样,也就是将Host CPU中同属一个core的threads分配给VM CPU的同一个core。注意不要把所有的threads都分配给VM,以免造成Host卡顿从而达到相反的效果。这里Host CPU是大小核架构,按照个人需求,将所有的小核(E-core)和一颗大核(P-core)分配给VM:
<vcpu placement="static">8</vcpu>
<iothreads>1</iothreads>
<cputune>
<vcpupin vcpu='0' cpuset='12'/>
<vcpupin vcpu='1' cpuset='13'/>
<vcpupin vcpu='2' cpuset='14'/>
<vcpupin vcpu='3' cpuset='15'/>
<vcpupin vcpu='4' cpuset='16'/>
<vcpupin vcpu='5' cpuset='17'/>
<vcpupin vcpu='6' cpuset='18'/>
<vcpupin vcpu='7' cpuset='19'/>
<emulatorpin cpuset='10-11'/>
<iothreadpin iothread='1' cpuset='10-11'/>
</cputune>
...
<cpu mode="host-passthrough" check="none" migratable="on">
<topology sockets="1" dies="1" cores="8" threads="1"/>
<cache mode="passthrough"/>
<maxphysaddr mode="passthrough" limit="40"/>
</cpu>
配置中的cpuset对应逻辑CPU(用小写的cpu表示逻辑CPU),对应lstopo
命令中PU P#的概念,即Processing Unit Processor,是CPU每个core中的处理单元,也就是超线程技术中的thread。在大小核架构下,一个大核拥有两个处理单元,一个小核只有一个。VM配置中emulatorpin和iothreadpin是I/O相关线程,在存在大量I/O请求的场景下应该pin不同的cpu,对于普通的Win10 VM,I/O并不密集,因此pin相同的cpu以节省资源。但要注意这两者不要和vcpupin的cpu有重叠,否则会降低VM运行性能。
CPU pin只是第一步,后续要确保Host不在这些pin过的cpu上分配任务,从而不跟VM抢占资源,需要进行cpu隔离。可以用Libvirt hook来实现,详见3.1 Cpu隔离Hook。
2.3 内存调优
QEMU/KVM默认使用2MB的透明大页内存,在VM启动时将根据配置的VM内存大小,自动锁定相应数量的内存大页,让VM独占以提升性能。本人使用的Ubuntu 23.10内核已将大页设置为madvise
模式,即系统默认启用透明大页,无需对大页配置进行更改。需确保在VM启动时有足够多的连续2MB内存,可供QEMU/KVM申请足够数量的内存大页即可。该需求可以通过3.2 内存优化Hook实现,该hook用来对Host内存进行回收、压缩以释放足够内存空间。查看此Arch Wiki以了解更多大页相关配置。
3 Libvirt Hooks
Libvirt hook提供一种在libvirt服务某个生命周期执行特定脚本的能力,hook脚本放置在/etc/libvirt/hooks目录,关于VM管理的脚本入口文件是/etc/libvirt/hooks/qemu,其默认用法如下:
/etc/libvirt/hooks/qemu $vm_name $hook_name $sub_name $extra
更多信息请查阅:Libvirt文档。要使用这个hook,需要判断VM实例名称、hook名称、子动作名称等参数,颇为不便。我在这里使用VFIO-Tools Hook Helper对hook使用流程进行简化。Libvirt hook helper实际上是一个脚本,内容如下:
#!/usr/bin/env bash
#
# Author: SharkWipf
#
# Copy this file to /etc/libvirt/hooks, make sure it's called "qemu".
# After this file is installed, restart libvirt.
# From now on, you can easily add per-guest qemu hooks.
# Add your hooks in /etc/libvirt/hooks/qemu.d/vm_name/hook_name/state_name.
# For a list of available hooks, please refer to https://www.libvirt.org/hooks.html
#
GUEST_NAME="$1"
HOOK_NAME="$2"
STATE_NAME="$3"
MISC="${@:4}"
BASEDIR="$(dirname $0)"
HOOKPATH="$BASEDIR/qemu.d/$GUEST_NAME/$HOOK_NAME/$STATE_NAME"
set -e # If a script exits with an error, we should as well.
# check if it's a non-empty executable file
if [ -f "$HOOKPATH" ] && [ -s "$HOOKPATH" ] && [ -x "$HOOKPATH" ]; then
eval \"$HOOKPATH\" "$@"
elif [ -d "$HOOKPATH" ]; then
while read file; do
# check for null string
if [ ! -z "$file" ]; then
eval \"$file\" "$@"
fi
done <<< "$(find -L "$HOOKPATH" -maxdepth 1 -type f -executable -print;)"
fi
简单来说就是优化了hook脚本的管理方式,安装完hook helper后重启libvirtd服务,即可通过如下结构管理VM hook:
/etc/libvirt/hooks/qemu.d/$vm_name/$hook_name/$sub_name/*
例如名称为win10的VM,其prepare hook、begin子动作要执行的脚本是setup.sh,则将脚本放在如下位置:
/etc/libvirt/hooks/qemu.d/win10/prepare/begin/setup.sh
Hook数量不限,类型不限定是shell脚本,在hashbbang中指定任何解释器均可。较为重要的几个hook类型如下:
# Before a VM is started, before resources are allocated:
/etc/libvirt/hooks/qemu.d/$vm_name/prepare/begin/*
# Before a VM is started, after resources are allocated:
/etc/libvirt/hooks/qemu.d/$vm_name/start/begin/*
# After a VM has started up:
/etc/libvirt/hooks/qemu.d/$vm_name/started/begin/*
# After a VM has shut down, before releasing its resources:
/etc/libvirt/hooks/qemu.d/$vm_name/stopped/end/*
# After a VM has shut down, after resources are released:
/etc/libvirt/hooks/qemu.d/$vm_name/release/end/*
以上述Win10 VM为例,创建若干启动和停止hook,分别用来实现cpu隔离、大页分配和cpu释放等操作自动化。
3.1 Cpu隔离Hook
创建脚本/etc/libvirt/hooks/qemu.d/win10/isolate-cpus.sh
,用于实现cpu隔离和恢复(只需隔离vcpupin配置的cpu):
#!/usr/bin/env bash
#
# Original author: Rokas Kupstys <rokups@zoho.com>
# Heavily modified by: Danny Lin <danny@kdrag0n.dev>
# And by ME: https://github.com/yjzzjy4
#
# Use systemd to isolate pinned cpus.
#
# Target file locations:
# - $SYSCONFDIR/hooks/qemu.d/$vm_name/prepare/begin/isolate-cpus.sh
# - $SYSCONFDIR/hooks/qemu.d/$vm_name/release/end/isolate-cpus.sh
# $SYSCONFDIR usually is /etc/libvirt.
#
ALL_CPUS='0-19'
HOST_CPUS='0-11' # Cpus reserved for host
VIRT_CPUS='12-19' # Cpus reserved for virtual machine(s)
VM_NAME="$1"
VM_ACTION="$2/$3"
function isolate_cpus() {
systemctl set-property --runtime -- user.slice AllowedCPUs=$HOST_CPUS
systemctl set-property --runtime -- system.slice AllowedCPUs=$HOST_CPUS
systemctl set-property --runtime -- init.scope AllowedCPUs=$HOST_CPUS
}
function unisolate_cpus() {
systemctl set-property --runtime -- user.slice AllowedCPUs=$ALL_CPUS
systemctl set-property --runtime -- system.slice AllowedCPUs=$ALL_CPUS
systemctl set-property --runtime -- init.scope AllowedCPUs=$ALL_CPUS
}
# For convenient manual invocation
if [[ "$VM_NAME" == "shield" ]]; then
isolate_cpus
exit 0
elif [[ "$VM_NAME" == "unshield" ]]; then
unisolate_cpus
exit 0
fi
if [[ "$VM_ACTION" == "prepare/begin" ]]; then
echo "libvirt-qemu systemd: Reserving CPUs $VIRT_CPUS for VM $VM_NAME" > /dev/kmsg 2>&1
isolate_cpus > /dev/kmsg 2>&1
echo "libvirt-qemu systemd: Successfully reserved CPUs $VIRT_CPUS" > /dev/kmsg 2>&1
elif [[ "$VM_ACTION" == "release/end" ]]; then
echo "libvirt-qemu systemd: Releasing CPUs $VIRT_CPUS from VM $VM_NAME" > /dev/kmsg 2>&1
unisolate_cpus > /dev/kmsg 2>&1
echo "libvirt-qemu systemd: Successfully released CPUs $VIRT_CPUS" > /dev/kmsg 2>&1
fi
3.2 内存优化Hook
创建脚本/etc/libvirt/hooks/qemu.d/win10/better-hugepages.sh
,用于回收并压缩内存,使VM启动时有足够的连续内存作为透明大页使用:
#!/usr/bin/env bash
#
# Author: SharkWipf (https://github.com/SharkWipf)
#
# This file depends on the PassthroughPOST hook helper script found here:
# https://github.com/PassthroughPOST/VFIO-Tools/tree/master/libvirt_hooks
# This hook only needs to run on `prepare/begin`, not on stop.
# Place this script in this directory:
# $SYSCONFDIR/libvirt/hooks/qemu.d/your_vm/prepare/begin/
# $SYSCONFDIR usually is /etc/libvirt.
#
# This hook will help free and compact memory to ease THP allocation.
# QEMU VMs will use THP (Transparent HugePages) by default if enough
# unfragmented memory can be found on startup. If your memory is very
# fragmented, this may cause a slow VM startup (like a slowly responding
# VM start button/command), and may cause QEMU to fall back to regular
# memory pages, slowing down VM performance.
# If you (suspect you) suffer from this, this hook will help ease THP
# allocation so you don't need to resort to misexplained placebo scripts.
#
# Don't use the old hugepages.sh script in this repo. It's useless.
# It's only kept in for archival reasons and offers no benefits.
#
# Finish writing any outstanding writes to disk.
sync
# Drop all filesystem caches to free up more memory.
echo 3 > /proc/sys/vm/drop_caches
# Do another run of writing any possible new outstanding writes.
sync
# Tell the kernel to "defragment" memory where possible.
echo 1 > /proc/sys/vm/compact_memory
由于使用的是动态大页,VM关闭时Host系统会自动回收大页内存,因此better-hugepages.sh
脚本仅需在VM启动时执行。上述两个脚本源自VFIO-Tools,我将第一个脚本修改为使用systemd隔离cpu,要使用cset,请参阅原地址。
3.3 线性阵列启停Hook
根据此前的配置,Win10 VM的系统硬盘是一个线性软阵列。该阵列在Host重启后将不复存在,需要再次运行start-md0.sh
脚本启动阵列。一个更好的做法是在VM启动时自动启动阵列,VM关闭后停止阵列,可以使用hook脚本实现该需求。将start-md0.sh
和stop-md0.sh
整合成一个hook脚本,/etc/libvirt/hooks/qemu.d/win10/manage-vdisk.sh
:
#!/usr/bin/env bash
#
# Author: yjzzjy4 (https://github.com/yjzzjy4)
#
# This file creates and distroys /dev/md0 for booting physical Windows drive.
#
WIN_PART=/dev/nvme1n1p2
EFI_DIR=/etc/libvirt/hooks/qemu.d/win10/md0
VM_ACTION="$2/$3"
if [[ "$VM_ACTION" == "prepare/begin" ]]; then
if [[ -e /dev/md0 ]]; then
echo "/dev/md0 already exists" > /dev/kmsg 2>&1
exit 1
fi
if mountpoint -q -- "${WIN_PART}"; then
echo "Unmounting ${WIN_PART}..." > /dev/kmsg 2>&1
umount ${WIN_PART}
fi
modprobe loop
modprobe linear
LOOP0=$(losetup -f "${EFI_DIR}/loop-efi0" --show)
LOOP1=$(losetup -f "${EFI_DIR}/loop-efi1" --show)
mdadm --build --verbose /dev/md0 --chunk=512 --level=linear --raid-devices=3 ${LOOP0} ${WIN_PART} ${LOOP1}
chown $USER:disk /dev/md0
echo "$LOOP0 $LOOP1" > "${EFI_DIR}/.win10-loop-devices"
elif [[ "$VM_ACTION" == "release/end" ]]; then
mdadm --stop /dev/md0
xargs losetup -d < "${EFI_DIR}/.win10-loop-devices"
fi
3.4 测试Hooks
将创建的所有hooks整理一下,利用软链接的形式存放到Win10 VM对应的生命周期目录中,最后得到如下结构:
$ tree -ah /etc/libvirt/hooks/qemu.d/win10
[4.0K] /etc/libvirt/hooks/qemu.d/win10
├── [1.4K] better-hugepages.sh
├── [1.7K] isolate-cpus.sh
├── [ 950] manage-vdisk.sh
├── [4.0K] md0
│ ├── [100M] loop-efi0
│ ├── [1.0M] loop-efi1
│ └── [ 23] .win10-loop-devices
├── [4.0K] prepare
│ └── [4.0K] begin
│ ├── [ 21] 00-manage-vdisk.sh -> ../../manage-vdisk.sh
│ ├── [ 25] 01-better-hugepages.sh -> ../../better-hugepages.sh
│ └── [ 21] 02-isolate-cpus.sh -> ../../isolate-cpus.sh
└── [4.0K] release
└── [4.0K] end
├── [ 21] 00-isolate-cpus.sh -> ../../isolate-cpus.sh
└── [ 21] 01-manage-vdisk.sh -> ../../manage-vdisk.sh
6 directories, 11 files
注意这里没有写关于自动挂载SMB的hook,因为找不到一个合适的生命周期用于自动挂载。并且由于挂载操作是用户级的,且Host作为SMB的client。故建议需要使用的用户随用随挂载,只需运行一行命令(或者在文件管理器中点一下),操作简单。在VM关机之前先将SMB卸载即可,不卸载也没问题,反正也访问不了(报错:Host is down)。
在启动Win10 VM之前,先记录一下系统状态,方便和启动后进行对比以验证hooks是否生效。首先是大页分配情况:
$ grep AnonHugePages /proc/meminfo
AnonHugePages: 0 kB
可知系统此时没有使用任何大页内存。再查看dev/md0
是否存在:
$ ls -al /dev | grep md0
未见任何输出,表示dev/md0
不存在。最后查看CPU拓扑:
$ lstopo-no-graphics --no-bridges --no-io
Machine (31GB total) + Package L#0
NUMANode L#0 (P#0 31GB)
L3 L#0 (24MB)
L2 L#0 (1280KB) + L1d L#0 (48KB) + L1i L#0 (32KB) + Core L#0
PU L#0 (P#0)
PU L#1 (P#1)
L2 L#1 (1280KB) + L1d L#1 (48KB) + L1i L#1 (32KB) + Core L#1
PU L#2 (P#2)
PU L#3 (P#3)
L2 L#2 (1280KB) + L1d L#2 (48KB) + L1i L#2 (32KB) + Core L#2
PU L#4 (P#4)
PU L#5 (P#5)
L2 L#3 (1280KB) + L1d L#3 (48KB) + L1i L#3 (32KB) + Core L#3
PU L#6 (P#6)
PU L#7 (P#7)
L2 L#4 (1280KB) + L1d L#4 (48KB) + L1i L#4 (32KB) + Core L#4
PU L#8 (P#8)
PU L#9 (P#9)
L2 L#5 (1280KB) + L1d L#5 (48KB) + L1i L#5 (32KB) + Core L#5
PU L#10 (P#10)
PU L#11 (P#11)
L2 L#6 (2048KB)
L1d L#6 (32KB) + L1i L#6 (64KB) + Core L#6 + PU L#12 (P#12)
L1d L#7 (32KB) + L1i L#7 (64KB) + Core L#7 + PU L#13 (P#13)
L1d L#8 (32KB) + L1i L#8 (64KB) + Core L#8 + PU L#14 (P#14)
L1d L#9 (32KB) + L1i L#9 (64KB) + Core L#9 + PU L#15 (P#15)
L2 L#7 (2048KB)
L1d L#10 (32KB) + L1i L#10 (64KB) + Core L#10 + PU L#16 (P#16)
L1d L#11 (32KB) + L1i L#11 (64KB) + Core L#11 + PU L#17 (P#17)
L1d L#12 (32KB) + L1i L#12 (64KB) + Core L#12 + PU L#18 (P#18)
L1d L#13 (32KB) + L1i L#13 (64KB) + Core L#13 + PU L#19 (P#19)
Host系统可见所有的cpu,也可以使用所有的cpu。然后我们启动Win10 VM:
$ virsh start win10
Domain 'win10' started
再次检查大页分配情况:
$ grep AnonHugePages /proc/meminfo
AnonHugePages: 8392704 kB
8392704KB=8196MB,Host系统为Win10 VM分配了8GB内存,即8192MB,说明QEMU/KVM在VM启动时已经锁定足额大页内存。然后查看dev/md0
是否存在:
$ ls -al /dev | grep md0
brw-rw---- 1 root disk 9, 0 4月 25 23:16 md0
brw-rw---- 1 root disk 259, 7 4月 25 23:16 md0p1
brw-rw---- 1 root disk 259, 8 4月 25 23:16 md0p2
结果表明线性阵列dev/md0
已存在,并被Win10 VM作为系统盘使用。最后查看CPU拓扑:
$ lstopo-no-graphics --no-bridges --no-io
Machine (31GB total) + Package L#0
NUMANode L#0 (P#0 31GB)
L3 L#0 (24MB)
L2 L#0 (1280KB) + L1d L#0 (48KB) + L1i L#0 (32KB) + Core L#0
PU L#0 (P#0)
PU L#1 (P#1)
L2 L#1 (1280KB) + L1d L#1 (48KB) + L1i L#1 (32KB) + Core L#1
PU L#2 (P#2)
PU L#3 (P#3)
L2 L#2 (1280KB) + L1d L#2 (48KB) + L1i L#2 (32KB) + Core L#2
PU L#4 (P#4)
PU L#5 (P#5)
L2 L#3 (1280KB) + L1d L#3 (48KB) + L1i L#3 (32KB) + Core L#3
PU L#6 (P#6)
PU L#7 (P#7)
L2 L#4 (1280KB) + L1d L#4 (48KB) + L1i L#4 (32KB) + Core L#4
PU L#8 (P#8)
PU L#9 (P#9)
L2 L#5 (1280KB) + L1d L#5 (48KB) + L1i L#5 (32KB) + Core L#5
PU L#10 (P#10)
PU L#11 (P#11)
此时Host的操作系统只能“看见”大核,所有小核正在被VM独占,因此在Host视角下,小核已经全部消失了。最后附上一张Host和VM的截图(两个系统分别在两个分辨率不同的显示器上,故截图有一部分是黑边):
可以看到Win10 VM拥有8个核心、8个线程和8GB内存。CPU型号识别正常,且C:和D:驱动器均是QEMU的SCSI设备。关于SMB共享,也可以通过文件管理器正常挂载,如图:
VM关闭后,上述配置将被逆转,即在Host操作系统上恢复原样,为避免啰嗦,不再附加截图演示。至此所有hooks均生效,VM配置和性能调优均已完成。