3824 字
19 分钟
一机两用_基于NUMA与大页内存优化的虚拟化流程

封面来源:NUMA

需求分析#

  • 课题组现有一台工作站,用于运行ansys fluent仿真;

  • 由于多核并行存在的边际效应,使用中发现仅使用一半的核即可达到近似的计算速度.

  • 拟将一台物理机拆分为两台虚拟机,供两人同时使用.

硬件配置与网络环境#

  • 硬件配置:课题组现有工作站.
硬件类型名称
CPUAMD TR 3990X (64核128线程)
主板华硕TRX 40 PRO S
内存128G-DDR4 2133
显卡3060Ti
硬盘1× 1T SSD + 1× 4T SSD
  • 网络环境:局域网环境,配置流程中需要上游路由为虚拟机分配静态内网地址.

整体配置思路#

  • 系统架构:底层虚拟环境为PVE,开放使用的工作终端为2台Win10虚拟机;
  • 磁盘分区
物理盘序号分区大小分区类型分区作用
1 (1TB)30GLVMPVE系统+ISO镜像
1 (1TB)800GLVM-thin作为两台虚拟机的系统盘(C:)
2 (4TB)4TLVM-thin作为两台虚拟机的数据盘(D:)

搭建流程#

PVE虚拟环境安装#

  • 安装PVE 9.1版本;
  • 在选择安装磁盘时,务必在选项中设置”MAX ROOT”参数:
    • 这一选项限制了PVE系统所占用的分区大小,可留下更多空间给虚拟机;
    • 一般30GB完全足够,不够用后续是可以扩展的.
  • 其它安装流程较为常规,不赘述.
TIP

限制PVE分区大小,为虚拟机留下更多空间.

PVE初始配置#

  1. 换源&包更新:参考与,将PVE源与Debian源更换到国内1;使用以下命令进行包更新.
Terminal window
apt update && apt upgrade -y
  1. 硬盘分区:将4TB硬盘进行擦除与格式化,创建新的thinpool.
NOTE

潜在优化:图中的thinpool创建方式未给SSD留够OP,建议给SSD空出15%左右的未分区空间供主控使用.

硬盘分区

优化配置#

  • 进行NUMA初始配置,需要在安装虚拟机前完成.

  • NUMA:“Non-uniform memory access”,非一致内存访问.

  • 主要目的:提升访存性能.

  • 工作原理:将系统内存划分为多块,将各块分配给访问延迟最低的CPU核心,实现整体访存性能提升;跨NUMA节点的内存访问相对很慢.

  • 适用性分析

    • 本工作站分为两台独立虚拟机,相互之间不会进行内存访问;
    • 将原机器分为两个NUMA节点,将每一台虚拟机分别绑定到其中一个节点,可以规避跨NUMA节点访存的劣势,最大化内存性能;
    • 虽然本机只有单颗CPU,但3990X CPU中本就分为多个CCD,可以分为多个NUMA节点.
  • 配置流程

  1. 配置BIOS:在主板BIOS中修改NUMA设置.

    • 该选项一般被称为”NUMA”、“Memory Node”或者”Memory Configuration”.
    • 一般可以设置为NPS1/NPS2/NPS4,代表每个插槽的节点数(Node per socket).
    • 此处需要两个NUMA节点,仅有一个CPU,选择”NPS2”.
    • 重启进入PVE,运行如下命令查询NUMA状态;对应的输出如下,说明该系统已被分为两个NUMA节点及各自对应的CPU核编号.
    Terminal window
    lscpu | grep -i numa

    NUMA node(s): 2
    NUMA node0 CPU(s): 0-31,64-95
    NUMA node1 CPU(s): 32-63,96-127

  2. 安装&使用numactl: numactl是一个NUMA策略监控管理工具,在PVE下使用如下命令安装与初始使用.

    • 可以查看到各node间的距离,可以发现跨节点内存访问的”距离”为12,节点内的为10.
Terminal window
apt update && apt install numactl -y && numactl --hardware

available: 2 nodes (0-1)
node 0 cpus : …
node 0 size: xxx MB
node 0 free: xxx MB
node1 …
node distances:
node 0 1
0: 10 12
1: 12 10

  1. NUMA前期准备完成,下一步工作在虚拟机创建后进行.

优化配置:大页内存#

  • 进行大页内存的初始配置,需要在安装虚拟机前完成.
  • 主要目的:提升访存性能.
  • 工作原理:将linux中的默认4KB page提升到1GB,大大减小页表体积,降低访存cache miss的可能性,提升性能.
  • 潜在问题:本文使用的内存分配方法会在PVE系统启动时即将大页内存分配好,导致这一部分内存持续被占用,无法被PVE系统/其它虚拟机动态使用,但在本文场景中影响不大.
  • 配置流程
  1. 编辑grub

    • 打开grub文件

    nano /etc/default/grub

    • 修改GRUB_CMDLINE_LINUX_DEFAULT行,在现有参数后添加:default_hugepagesz=1G hugepagesz=1G hugepages=120(其中120是大页数量,按实际内存数量修改).记得给PVE系统预留空间.
    IMPORTANT

    大页数量不可等于系统总内存,一定要为PVE系统预留!否则会导致系统反复重启,只能重装.

  2. 结合NUMA节点分配内存块

    • 通过以下命令,在PVE系统启动时将大页平均分配到两个NUMA节点.其中的60是每节点大页数量,为①中总页数的一半.
    Terminal window
    echo "echo 60 > /sys/devices/system/node/node0/hugepages/hugepages-1048576kB/nr_hugepages" | tee -a /etc/rc.local
    echo "echo 60 > /sys/devices/system/node/node1/hugepages/hugepages-1048576kB/nr_hugepages" | tee -a /etc/rc.local
    update-grub # 更新grub
    reboot # 重启系统
  3. 确认大页内存已分配

    • 系统重启后,使用以下命令查看各个节点上的大页数量,应当与上一步的设置一致.
    Terminal window
    cat /sys/devices/system/node/node*/hugepages/hugepages-1048576kB/free_hugepages

    预期输出:

    60 60

  4. 大页内存前期准备完成,下一步工作在虚拟机创建后进行.

创建虚拟机#

  • 以下步骤用于创建虚拟终端.
  1. 将Windows 10 ISO镜像和VirtIO驱动2上传到PVE系统.
  1. 点击PVE界面右上角的创建虚拟机选项.

    • 常规界面
    CreateVM_Regular
    • 操作系统界面
    CreateVM_OS
    • 系统界面:将TPM存储与EFI存储设定到local_lvm卷,与虚拟Windows安装到同一块物理SSD上.
    • 磁盘界面:将scsi0的磁盘大小设置为400G,存储选择在local_lvm;左下角点击”添加”,新scsi1磁盘的存储选择在硬盘分区一节新建的存储池,大小设置为1800G.
    TIP

    潜在优化:此处也可先设置scsi0大小为128G,且不添加scsi1;待系统整备完成后,可以先备份一个最小系统镜像,再扩展分区.

    • CPU界面:两台虚拟机设置略有差别,需要绑定到不同的CPU核心.
    create_vm_3_CPU
    • 内存界面:容量与大页大小保持一致(如50个大页,就填写50*1024=51200 MB),取消勾选Ballooning.
    • 网络界面:保持默认,可按需设置虚拟网卡MAC地址.
    • 创建虚拟机,创建后先不要启动,需要进行额外配置.
  2. 额外配置

    • 修改PVE后台虚拟机配置文件.
    • 打开位于/etc/pve/qemu-server文件夹下的虚拟机配置文件,文件名为”<vm_id>.conf”,其中vm_id为上一步”常规”选项卡中的虚拟机ID.
    • 参考下图设置,只要修改有注释的行,如果对应行不存在可以自行添加.
create_vm_4_conf_file
  1. 验证配置正确性.
    • 启动虚拟机后,按序执行下述指令;如得到期待结果,则说明NUMA与大页内存配置成功.
Terminal window
# 查询大页剩余
grep "" /sys/devices/system/node/node*/hugepages/hugepages-1048576kB/free_hugepages
# 在虚拟机启动后,两个node上的hugepages计数都应降为0.
# 验证内存-NUMA节点绑定情况
# 获取虚拟机PID: 将 101 替换为实际VM_ID
ps aux | grep "/usr/bin/kvm -id 101" | grep -v grep | awk '{print $2}' # 返回的数字为虚拟机PID
# 查看内存使用情况
numastat -p 12345 # 12345是刚刚查询到的虚拟机PID
# 如果配置成功,该虚拟机的绝大部分内存会集中在其对应的node上,只有很少一部分跨node.
# 典型的情况如下.
# Per-node process memory usage (in MBs) for PID 31529 (kvm)
# Node 0 Node 1 Total
# --------------- --------------- ---------------
# Huge 0.00 59392.00 59392.00
# Heap 0.00 27.23 27.23
# Stack 0.00 0.72 0.72
# Private 13.46 63.11 76.57
# ---------------- --------------- --------------- ---------------
# Total 13.46 59483.07 59496.52
  1. 安装虚拟机操作系统.
    • 按照常规Windows安装流程即可.在选择安装磁盘步骤,需要先加载VirtIO带”w10”字样的驱动,才能找到磁盘.
TIP

在选择安装磁盘步骤,选择对应的VirtIO驱动来找到虚拟磁盘.

进行USB直通#

  • 工作目的:使用U盘向虚拟机直接传输数据.
  • 工作思路:直通USB控制器直通到虚拟机.
  • 技术选型:常见的USB直通方案有按设备直通、按控制器直通.
    • 按设备直通:在PVE中选择”硬件->添加->USB设备”进行添加.
    • 按控制器直通:在PVE中选择”硬件->添加->PCI设备”进行添加.
    • 其中,前者设置简单,不需要重启 PVE,但是使用新U盘时由于U盘ID变化,需要重新操作;后者直接让虚拟机识别原生的 USB 控制器,无性能损耗,且只要将U盘插到对应的数个物理接口上即能自动识别.本文选用按控制器直通方案.

正常流程#

  1. 硬件启用iommu:进入主板BIOS.
    • Intel: 打开VT-d / Virtualization Technology.
    • AMD: 打开iommu / AMD-Vi / SVM Mode.
  2. 软件启用iommu
Terminal window
# 打开pve的grub配置文件
vi /etc/default/grub
# 在原有GRUB_CMDLINE_LINUX_DEFAULT参数后添加:
# intel :"intel_iommu=on iommu=pt"
# amd: "amd_iommu=on iommu=pt"
# 修改后的选项可能如:"quiet default_hugepagesz=1G hugepagesz=1G hugepages=116 amd_iommu=on iommu=pt"
update-grub # 更新grub
# 加载直通模块
echo "vfio" | tee -a /etc/modules-load.d/vfio.conf
echo "vfio_iommu_type1" | tee -a /etc/modules-load.d/vfio.conf
echo "vfio_pci" | tee -a /etc/modules-load.d/vfio.conf
echo "vfio_virqfd" | tee -a /etc/modules-load.d/vfio.conf
update-initramfs -u -k all
# 重启并检查是否成功.
reboot
dmesg | grep -e DMAR -e IOMMU # 如果结果里有"IOMMU enabled"或者"Detected AMD IOMMU #0"等字样说明成功.
  1. 查看硬件分组
Terminal window
lspci -nnk | grep USB # 查看USB硬件
# 可能的返回值
# 03:00.3 USB controller [0c03]: Advanced Micro Devices, Inc. [AMD] Starship USB 3.0 Host Controller [1022:148c]
# 22:00.3 USB controller [0c03]: Advanced Micro Devices, Inc. [AMD] Starship USB 3.0 Host Controller [1022:148c]
# 44:00.1 USB controller [0c03]: Advanced Micro Devices, Inc. [AMD] Matisse USB 3.0 Host Controller [1022:149c]
# 44:00.3 USB controller [0c03]: Advanced Micro Devices, Inc. [AMD] Matisse USB 3.0 Host Controller [1022:149c]
# 说明该系统具有4个USB控制器,总线地址为03:00.3等.
# 但部分控制器可能共享IOMMU分组,这说明它们的物理电路相捆绑,只能按组直通,不能直通一半/分给两台VM.
# 使用以下命令确认,最后的总线编号参照上方返回值修改.
printf '%s\n' /sys/kernel/iommu_groups/*/devices/* | while read -r d; do echo "Group ${d%/devices/*}; ${d##*/}; $(lspci -nns ${d##*/})"; done | grep -E "03:00.3|22:00.3|44:00.1|44:00.3"
# 可能的返回值如下:
# Group /sys/kernel/iommu_groups/24; 0000:44:00.1; 44:00.1 USB controller [0c03]: Advanced Micro Devices, Inc. [AMD] Matisse USB 3.0 Host Controller [1022:149c]
# Group /sys/kernel/iommu_groups/24; 0000:44:00.3; 44:00.3 USB controller [0c03]: Advanced Micro Devices, Inc. [AMD] Matisse USB 3.0 Host Controller [1022:149c]
# Group /sys/kernel/iommu_groups/43; 0000:22:00.3; 22:00.3 USB controller [0c03]: Advanced Micro Devices, Inc. [AMD] Starship USB 3.0 Host Controller [1022:148c]
# Group /sys/kernel/iommu_groups/60; 0000:03:00.3; 03:00.3 USB controller [0c03]: Advanced Micro Devices, Inc. [AMD] Starship USB 3.0 Host Controller [1022:148c]
# 注意到开头/sys/kernel/iommu_groups/xx中的xx是IOMMU分组,说明44:00开头的两个控制器在同一个IOMMU组,无法拆分.
# 同时,结尾的[1022:149c]是对应控制器的ids编号,处理意外情况时有用.
  1. 映射物理接口
Terminal window
# 将主板物理接口与总线编号进行对应.
# 首先将接口与总线进行对应.
watch -n 1 "lsusb -t"
# 先运行上述指令,再准备一个U盘,依次序插入主板的各个物理接口.
# 在插入后,某个总线上会多出一个设备,此即该物理口所对应的USB总线.
# 反复操作,形成一张对应表,如:
# 物理接口编号 USB总线编号
# 前面板1号口 USB1
# 前面板2号口 USB1
# 后面板一号口 USB2
# ...
# 对应USB Bus与PCI地址.
grep . /sys/bus/usb/devices/usb*/../../uevent
# 其返回结果如下:
# ....(很多其它信息)
# /sys/bus/usb/devices/usb1/../../uevent:PCI_SLOT_NAME=0000:03:00.3 # usb1的PCI总线编号
# ....
# 将每个usb bus与PCI总线的对应关系记录下来,如下:
# 物理接口编号 USB总线编号 对应PCI总线编号
# 前面板1号口 USB1 0000:00:08.1
# 前面板2号口 USB1 0000:00:08.1
# 后面板一号口 USB2 0000:20:08.1
# ...
# 形成对应表后,可以进行下一步;但有时候会找不到具有对应编号的设备,请参阅 "意外情况-找不到具有对应PCI ID的设备" 一节.
  1. 控制器直通
    • 打开PVE后台网页,打开对应虚拟机->硬件->添加->PCI设备选项卡.
    • 直通对应的控制器后,其下挂载的所有物理USB接口都会直接归于该虚拟机管理.

usb_direct_1_add_pcie

  1. **完成!**只需将U盘插入直通控制器控制的物理USB口,即可直连至虚拟机~

意外情况#

  • 意外1:找不到具有对应PCI ID的设备

    • 问题描述:在某些情况下,根据第三步PCI_SLOT_NAME,找不到对应的PCIE设备.

    • 问题原理:在 AMD 架构下,PCI_SLOT_NAME一般是USB 控制器的”父亲”(PCIe 桥/根端口),而不是控制器本身.

    • 解决方案

      Terminal window
      # 列举PCI设备树.
      lspci -t
      # 典型输出如下
      #-+-[0000:00]-+-00.0
      # ... 一堆设备
      # | +-08.1-[03]--+-00.0
      # | | \-00.3
      # ... 一堆设备
      # 这里可以看到,PCI_SLOT_NAME(0000:00:08.1)下面挂着(03:00.3),后者在PVE的PCIE设备列表中可以找到.
      # 转而选择对应的子Controller即可.
  • 意外2:直通后系统异常重启,或者直通成功但没效果

    • 问题原理平台下,宿主机的驱动可能存在bug,导致宿主机驱动(xhci_hcd)没有释放设备,虚拟机驱动(vfio-pci)便无法获取设备.

    • 解决方案:禁止宿主机加载USB驱动.

      IMPORTANT

      执行该步操作后,将只能通过网络界面访问PVE后台,直连PVE物理机的USB键盘/鼠标可能失效,请谨慎操作.

    • 操作步骤

Terminal window
# 参考"查看硬件分组"一节,找到要直通的USB控制器的ids编号(以1022:148c为例).
# 1.强制绑定到虚拟机驱动(vfio-pci)
# 编辑/创建配置文件
vim /etc/modprobe.d/vfio.conf
# 在文件中输入以下内容
options vfio-pci ids=1022:148c
# 先重启检查一下这样行不行.
update-initramfs -u -k all && reboot
# 重启后,在终端查询
lspci -nnk -s 22:00.3 # 22:00.3是对应的USB Controller编号,不一定是PCI_SLOT_NAME!
# 在输出中找到Kernel driver in use一行,如果显示vfio-pci则无需执行后续操作,进虚拟机看效果即可.
# 最终备选方案 :如果仍显示xhci,则需进行下面的屏蔽操作.
vim /etc/modprobe.d/pve-blacklist.conf
# 添加以下行
blacklist xhci_hcd
blacklist xhci_pci
# 更新内核并重启
update-initramfs -u -k all && reboot
# 重新查询,99.9%的概率都能成功变为vfio-pci.
lspci -nnk -s 22:00.3

改进思路#

本次配置虽然达到了目的,但也存在一些问题.

  • 问题1:未给SSD留OP空间.
    • 在创建lvm-thin分区时使用CLI操作,特意留下空闲空间.
  • 问题2:未留存装好工程软件的最小系统镜像,不方便恢复.
    • 遵循”创建小硬盘->安装软件->全盘备份->硬盘扩容”的流程安装新系统,方便系统恢复.

Footnotes#

  1. Proxmox软件仓库 | 清华大学镜像站

  2. archive-virtio / fedorapeople

一机两用_基于NUMA与大页内存优化的虚拟化流程
https://www.lithium-hydroxide.space/posts/251223_numa/
作者
LiH
发布于
2025-12-23
许可协议
CC BY-NC-SA 4.0