起因背景

我们在使用NVIDIA vGPU切分显卡资源给虚拟机后,这台虚拟机就会存在2个虚拟屏幕,一个是PVE的VNC窗口,一个是NVIDIA VGX虚拟屏幕,当我们在PVE的VNC窗口操作虚拟机的话,部分应用又无法调用到GPU资源,把VNC窗口设置“无”即NONE的话只能借助外部远程工具连接虚拟机,PVE的VNC窗口又不工作。

所以这里参考佛西:为vgpu设备添加mdev gpu类型的补丁给PVE9打上。让VNC窗口直接输出vGPU显卡画面。

支持类型

说明版本
系统PVE 9.1.6
内核Linux pve9 6.17.13-2-pve
pve-qemu-kvm10.0.2-7
支持虚拟机类型SeaBIOS/OVMF
正常工作版本vGPU11/16/17/18
不工作版本vGPU19

修改QemuServer.pm

可以使用nano命令来编辑QemuServer.pm文件
nano /usr/share/perl5/PVE/QemuServer.pm
1)第一处,在这句结尾加入mdev参数

        enum => [
-            qw(cirrus qxl qxl2 qxl3 qxl4 none serial0 serial1 serial2 serial3 std virtio virtio-gl vmware)
        ],

修改完成后效果:

        enum => [
+            qw(cirrus qxl qxl2 qxl3 qxl4 none serial0 serial1 serial2 serial3 std virtio virtio-gl vmware mdev)
        ],

2)第二处,在这句结尾加入'mdev' => 'mdev',参数

my $vga_map = {
    'cirrus' => 'cirrus-vga',
    'std' => 'VGA',
    'vmware' => 'vmware-svga',
    'virtio' => 'virtio-vga',
    'virtio-gl' => 'virtio-vga-gl',
};

修改完成后效果:

my $vga_map = {
    'cirrus' => 'cirrus-vga',
    'std' => 'VGA',
    'vmware' => 'vmware-svga',
    'virtio' => 'virtio-vga',
    'virtio-gl' => 'virtio-vga-gl',
+    'mdev' => 'mdev',
};

3)第三处,在这句中加入if ($vga->{type} ne 'mdev'){ if判断语句,别忘了后面还有个括号}参数

push @$cmd, '-no-reboot' if defined($conf->{reboot}) && $conf->{reboot} == 0;
    if ($vga->{type} && $vga->{type} !~ m/^serial\d+$/ && $vga->{type} ne 'none') {
    push @$devices, '-device',
        print_vga_device($conf, $vga, $arch, $machine_version, undef, $qxlnum, $bridges);
    push @$cmd, '-display', 'egl-headless,gl=core' if $vga->{type} eq 'virtio-gl'; # VIRGL

修改完成后效果:

push @$cmd, '-no-reboot' if defined($conf->{reboot}) && $conf->{reboot} == 0;
    if ($vga->{type} && $vga->{type} !~ m/^serial\d+$/ && $vga->{type} ne 'none') {
+    if ($vga->{type} ne 'mdev'){
    push @$devices, '-device',
        print_vga_device($conf, $vga, $arch, $machine_version, undef, $qxlnum, $bridges);
+    }
    push @$cmd, '-display', 'egl-headless,gl=core' if $vga->{type} eq 'virtio-gl'; # VIRGL

4)第四处,在这句结尾加入|mdev参数,让Mdev显示器支持spcie协议,如果要用SPICE协议云桌面办公的建议加上(可选)

   my $spice_port;

    assert_clipboard_config($vga);
-    my $is_spice = $qxlnum || $vga->{type} =~ /^virtio/;

修改完成后效果:

   my $spice_port;

    assert_clipboard_config($vga);
+    my $is_spice = $qxlnum || $vga->{type} =~ /^(virtio|mdev)/;

修改PCI.pm

可以使用nano命令来编辑PCI.pm文件
nano /usr/share/perl5/PVE/QemuServer/PCI.pm
1)第一处,在这2句中间加入mdev类型的判断处理

            my $mf_addr = $multifunction ? ".$j" : '';
            $devicestr .= ",id=${id}${mf_addr}${pciaddr}${mf_addr}";

            if ($j == 0) {
                $devicestr .= ',rombar=0' if defined($d->{rombar}) && !$d->{rombar};
                $devicestr .= "$xvga";

修改完成后效果:

            my $mf_addr = $multifunction ? ".$j" : '';
            $devicestr .= ",id=${id}${mf_addr}${pciaddr}${mf_addr}";

+        my $mdevtype = $d->{mdev} // undef;
+        if ($mdevtype =~ /^(.*?)-/) {
+            $mdevtype = $1;
+        }

            if ($j == 0) {
                $devicestr .= ',rombar=0' if defined($d->{rombar}) && !$d->{rombar};
                $devicestr .= "$xvga";

2)第二处,在这2句中间加入mdev类型的判断处理

            if ($j == 0) {
                $devicestr .= ',rombar=0' if defined($d->{rombar}) && !$d->{rombar};
                $devicestr .= "$xvga";
                $devicestr .= ",multifunction=on" if $multifunction;
                $devicestr .= ",romfile=/usr/share/kvm/$d->{romfile}" if $d->{romfile};
                $devicestr .= ",bootindex=$bootorder->{$id}" if $bootorder->{$id};
                for my $option (qw(vendor-id device-id sub-vendor-id sub-device-id)) {
                    $devicestr .= ",x-pci-$option=$d->{$option}" if $d->{$option};
                }
            }      
            push @$devices, '-device', $devicestr;
            last if $d->{mdev};
        }
    }

修改完成后效果:

            if ($j == 0) {
                $devicestr .= ',rombar=0' if defined($d->{rombar}) && !$d->{rombar};
                $devicestr .= "$xvga";
                $devicestr .= ",multifunction=on" if $multifunction;
                $devicestr .= ",romfile=/usr/share/kvm/$d->{romfile}" if $d->{romfile};
                $devicestr .= ",bootindex=$bootorder->{$id}" if $bootorder->{$id};
                for my $option (qw(vendor-id device-id sub-vendor-id sub-device-id)) {
                    $devicestr .= ",x-pci-$option=$d->{$option}" if $d->{$option};
                }
            }
+        if ($mdevtype && $vga->{type} eq 'mdev'){
+            $devicestr .= ",display=on,ramfb=on,driver=vfio-pci-nohotplug";
+        }
            push @$devices, '-device', $devicestr;
            last if $d->{mdev};
        }
    }

修改pvemanagerlib.js

可以使用nano命令来编辑pvemanagerlib.js文件
nano /usr/share/pve-manager/js/pvemanagerlib.js
修改前端文件,在这句结尾加入mdev: 'NVIDIA vGPU',参数

std: gettext('Standard VGA'),
            vmware: gettext('VMware compatible'),
            qxl: 'SPICE',
            qxl2: 'SPICE dual monitor',
            qxl3: 'SPICE three monitors',
            qxl4: 'SPICE four monitors',
            serial0: gettext('Serial terminal') + ' 0',
            serial1: gettext('Serial terminal') + ' 1',
            serial2: gettext('Serial terminal') + ' 2',
            serial3: gettext('Serial terminal') + ' 3',
            virtio: 'VirtIO-GPU',
            'virtio-gl': 'VirGL GPU',
+            mdev: 'NVIDIA vGPU',
            none: Proxmox.Utils.noneText,
        },

重启服务让其生效,重启完记得Ctrl+F5清理下浏览器缓存。

systemctl restart {pvedaemon,pveproxy}

最后就是给虚拟机添加上vGPU设备了,然后在显示那选择NVIDIA vGPU显示就能正常输入vGPU画面了。只测试了vGPU11 16 17 18的驱动版本可以正常显示,vGPU19 黑屏异常可能驱动版本过高导致。

一键修补脚本

懒人专属,附上个一键脚本进行修补,仅在PVE9.1.6版本测试通过,其他版本自测或者手动替换。

一键新增一个NVIDIA vGPU显示器脚本


使用方法:

# 使用nano命令新建一个patch-mdev.sh文件
nano patch-mdev.sh
# 粘贴下边的脚本
# 添加可执行权限
chmod +x patch-mdev.sh
# 运行脚本
./patch-mdev.sh

以下是脚本内容:

#!/bin/bash
set -e

echo "===== PVE mdev Patch====="

backup_file() {
    local file=$1
    if ls ${file}.bak.* 1>/dev/null 2>&1; then
        echo "  ↪ 已存在备份 $file"
    else
        cp "$file" "${file}.bak.$(date +%F-%H%M%S)"
        echo "[备份] $file"
    fi
}


patch_qemuserver() {
    local file="/usr/share/perl5/PVE/QemuServer.pm"

    backup_file "$file"

    echo "[处理] QemuServer.pm"

    # 1️⃣ 修改第一处
    if ! grep -q "vmware mdev)" "$file"; then
        sed -i "s/qw(\(.*vmware\))/qw(\1 mdev)/" "$file"
        echo "  ✔ 已修补完成"
    else
        echo "  ↪ 已存在,跳过"
    fi

    # 2️⃣ 修改第二处
    if ! grep -q "'mdev' => 'mdev'" "$file"; then
        sed -i "/'virtio-gl' => 'virtio-vga-gl',/a\    'mdev' => 'mdev'," "$file"
        echo "  ✔ 已修补完成"
    else
        echo "  ↪ 已存在,跳过"
    fi

    # 3️⃣ 修改第三处
    if ! grep -q "if (\$vga->{type} ne 'mdev')" "$file"; then
        echo "  ✔ 已修补完成"

        awk '
        {
            if ($0 ~ /push @\$devices, '\''-device'\''/ && !done) {
                getline nextline

                if (nextline ~ /print_vga_device/) {
                    print "    if ($vga->{type} ne '\''mdev'\''){"
                    print "        push @$devices, '\''-device'\'',"
                    print "" nextline
                    print "    }"
                    done=1
                    next
                } else {
                    print $0
                    print nextline
                    next
                }
            }

            print $0
        }
        ' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"

    else
        echo "  ↪ 已存在,跳过"
    fi

    # 4️⃣ 修改第四处
    if ! grep -q "virtio|mdev" "$file"; then
        echo "  ✔ 已修补完成"

        sed -i '/my \$is_spice = \$qxlnum || \$vga->{type} =~ \/\^virtio\//c\    my $is_spice = $qxlnum || $vga->{type} =~ /^(virtio|mdev)/;' "$file"
    else
        echo "  ↪ 已存在,跳过"
    fi
}

patch_pci() {
    local file="/usr/share/perl5/PVE/QemuServer/PCI.pm"
    backup_file "$file"

    echo "[处理] PCI.pm"

    # 1️⃣ 修改第一处
    if ! grep -q "mdevtype" "$file"; then
        sed -i "/id=\${id}/a\            my \$mdevtype = \$d->{mdev} \/\/ undef;\n            if (\$mdevtype =~ \/^(.*?)-\/) {\n                \$mdevtype = \$1;\n            }" "$file"
        echo "  ✔ 已修补完成"
    else
        echo "  ↪ 已存在,跳过"
    fi

    # 2️⃣ 修改第二处
    if ! grep -q "vfio-pci-nohotplug" "$file"; then
        awk '
        {
            print

            if ($0 ~ /if \(\$j == 0\)/) {
                in_block=1
                brace=0
            }

            if (in_block) {
                brace += gsub(/{/, "{")
                brace -= gsub(/}/, "}")

                if (brace == 0) {
                    print "        if ($mdevtype && $vga->{type} eq '\''mdev'\''){"
                    print "            $devicestr .= \",display=on,ramfb=on,driver=vfio-pci-nohotplug\";"
                    print "        }"
                    in_block=0
                }
            }
        }
        ' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
            echo "  ✔ 已修补完成"
    else
        echo "  ↪ 已存在,跳过"
    fi
}


patch_ui() {
    local file="/usr/share/pve-manager/js/pvemanagerlib.js"
    backup_file "$file"

    echo "[处理] 前端UI"

    if ! grep -q "mdev: 'NVIDIA vGPU'" "$file"; then
        sed -i "/'virtio-gl': 'VirGL GPU',/a\            mdev: 'NVIDIA vGPU'," "$file"
        echo "  ✔ 已修补完成"
    else
        echo "  ↪ 已存在,跳过"
    fi
}

restart_services() {
    echo "[重启服务]"
systemctl restart {pvedaemon,pveproxy}
}

# ===== 执行 =====
patch_qemuserver
patch_pci
patch_ui
restart_services

echo ""
echo "===== ✅ 完成 ====="
echo "请 Ctrl+F5 刷新浏览器"

广告:PROXMOX-VE 技术支持,疑难解答,有需要可以闲鱼与我联系↓↓↓

PVE技术支持
最后修改:2026 年 04 月 18 日
如果觉得我的文章对你有用,请随意赞赏