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分区表规范,最终要创建的线性阵列分区布局如下:

linear-array-layout

​ 接着将配置好的线性阵列作为一个整体供应给VM,作为其启动盘即可启动物理分区中的Win10系统。

​ 第1节主要参考资料:

  1. Boot physical Windows inside Qemu guest machine;
  2. Boot a Windows Partition From Linux Using KVM.

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,配置要点如下:

  1. 芯片组Q35,固件UEFI;
  2. 使用Windows ISO作为第一启动项;
  3. 使用/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-topo

​ 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.shstop-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的截图(两个系统分别在两个分辨率不同的显示器上,故截图有一部分是黑边):

system-info

​ 可以看到Win10 VM拥有8个核心、8个线程和8GB内存。CPU型号识别正常,且C:和D:驱动器均是QEMU的SCSI设备。关于SMB共享,也可以通过文件管理器正常挂载,如图:

smb-share-mount

​ VM关闭后,上述配置将被逆转,即在Host操作系统上恢复原样,为避免啰嗦,不再附加截图演示。至此所有hooks均生效,VM配置和性能调优均已完成。

posted @ 2024-04-26 17:22  窝法氦镁烷  阅读(637)  评论(2编辑  收藏  举报