🔧 一、创建Hookscript脚本

  1. 脚本位置
    将脚本存放在PVE可识别的存储目录中(如local存储的snippets子目录):

    bash

    # 创建存储目录(若不存在)
    mkdir -p /var/lib/vz/snippets/
    
    # 创建脚本文件(示例命名为vm-hook-nvme.sh)
    nano /var/lib/vz/snippets/vm-hook-nvme.sh
  2. 脚本内容(参考增强版)1

    bash

    #!/bin/bash
    DEV="0000:05:00.0"                     # 替换为实际PCI设备ID(通过lspci查看)
    HOST_DRIVER="nvme"                     # 宿主机驱动名(用lspci -k确认)
    VFIO_DRIVER="vfio-pci"
    STATUS_FILE="/tmp/vm_$1_running"
    
    # 异常状态恢复:若设备残留VFIO绑定且无运行标记,则强制绑回宿主机
    CURRENT_DRIVER=$(lspci -n -s $DEV | awk -F': ' '{print $2}' | cut -d' ' -f1)
    if [ "$CURRENT_DRIVER" == "$VFIO_DRIVER" ] && [ ! -f "$STATUS_FILE" ]; then
        echo "$DEV" > /sys/bus/pci/drivers/$VFIO_DRIVER/unbind 2>/dev/null
        echo "$DEV" > /sys/bus/pci/drivers/$HOST_DRIVER/bind 2>/dev/null
    fi
    
    case "$2" in
      pre-start)
        touch "$STATUS_FILE"
        echo "$DEV" > /sys/bus/pci/drivers/$HOST_DRIVER/unbind || echo "解绑宿主机驱动失败"
        echo "$DEV" > /sys/bus/pci/drivers/$VFIO_DRIVER/bind || echo "绑定VFIO失败"
        ;;
      post-stop)
        echo "$DEV" > /sys/bus/pci/drivers/$VFIO_DRIVER/unbind || echo "解绑VFIO失败"
        echo "$DEV" > /sys/bus/pci/drivers/$HOST_DRIVER/bind || echo "绑回宿主机失败"
        rm -f "$STATUS_FILE"
        ;;
    esac
    exit 0
    • ⚠️ 注意点

      • DEV:替换为设备PCI ID(如0000:0B:00.0

      • HOST_DRIVER:通过lspci -k -s <PCI-ID>查询实际驱动名(可能为nvmeahci等)

  3. 赋予执行权限

    bash

    chmod +x /var/lib/vz/snippets/vm-hook-nvme.sh

⚙️ 二、配置虚拟机Hookscript属性

  1. 通过CLI绑定脚本到虚拟机
    将脚本关联到目标虚拟机(假设VMID为100):

    bash

    qm set 100 -hookscript local:snippets/vm-hook-nvme.sh
    • local:snippets/: 指定PVE的local存储下snippets目录1

    • 成功后输出:update VM 100: -hookscript local:snippets/vm-hook-nvme.sh

  2. 验证配置
    检查虚拟机配置文件/etc/pve/qemu-server/100.conf,应包含:

    conf

    hookscript: local:snippets/vm-hook-nvme.sh

🔍 三、测试与验证

  1. 启动虚拟机(触发pre-start钩子)

    bash

    qm start 100
    # 检查设备驱动状态
    lspci -k -s 05:00.0 | grep driver  # 应显示vfio-pci
  2. 停止虚拟机(触发post-stop钩子)

    bash

    qm stop 100
    # 检查设备是否回归宿主机
    lspci -k -s 05:00.0 | grep driver  # 应显示nvme(或原驱动)

⚠️ 四、关键注意事项

  1. 权限问题

    • 脚本需以root权限执行,PVE自动以root调用钩子,无需额外配置。

  2. 驱动兼容性

    • 宿主机驱动名必须准确(通过lspci -k -s <PCI-ID>确认)1

  3. 设备冲突

    • 若设备已被宿主机占用(如挂载文件系统),unbind操作会失败。需确保设备未被使用:

      bash

      umount /dev/nvme0n1p1  # 示例:卸载分区
  4. 调试日志

    • 可追加日志记录到文件(调试时在脚本开头添加):

      bash

      exec &>> /var/log/vm-hook.log
      echo "[$(date)] VM $1: $2 phase executed"

📜 五、生命周期钩子说明

PVE支持的钩子阶段与脚本调用逻辑:

虚拟机状态

触发钩子

脚本行为

设备状态

启动前

pre-start

解绑宿主机 → 绑定VFIO

直通给虚拟机

停止后

post-stop

解绑VFIO → 绑回宿主机

回归宿主机

崩溃/强制停止

不触发钩子

依赖脚本开头的异常恢复逻辑

自动修复绑定状态

此配置实现了设备直通的自动化管理,覆盖正常开关机及异常恢复场景 ✅。