libvirt USB 设备直通虚拟机:在线热插拔,不硬编码 domain XML
九月 01, 2026 [虚拟化与容器] #libvirt #qemu #usblibvirt USB 设备直通虚拟机:在线热插拔,不硬编码 domain XML
背景
需要把一台 Android 手机(Pixel/Nexus,USB 连接模式为 "charging + debug",即 adb 调试)直通到本机 libvirt 虚拟机(debian13 / ubuntu26.04,均未运行)里使用。
Bus 001 Device 027: ID 18d1:4ee7 Google Inc. Nexus/Pixel Device (charging + debug)
问题来了:直通方式一旦硬编码进 domain XML(virsh edit 里的 <hostdev>),下次换手机(vendor/product 变化)就不生效,且 USB 设备不在场时 VM 启动会直接失败。有没有在线、灵活的方案?
方案:热插拔 + vendor/product 匹配,不写 domain XML
核心两点:
- 设备 XML 与虚拟机定义完全分离,只对运行中的 VM 生效(不
--persistent就不落盘); - 按 vendor/product 匹配,而不是 bus/device 地址——设备号(
027)每次拔插都会变,vendor/product 才是稳定标识。
设备 XML 模板:
<!-- usb-device.xml -->
<hostdev mode='subsystem' type='usb' managed='yes'>
<source>
<vendor id='0x18d1'/>
<product id='0x4ee7'/>
</source>
</hostdev>
操作命令:
virsh attach-device debian13 usb-device.xml # 热插上
virsh detach-device debian13 usb-device.xml # 热拔下
18d1:4ee7是 Google 设备的 adb 接口 ID:只要是 Pixel/Nexus 开 adb 调试就是这个组合,拔插、换 USB 口都不变(MTP 文件传输模式是另一个 ID,如18d1:4ee1,注意区分);- 换品牌手机:
lsusb查新 ID,改 XML 两个 id 后在线 attach 即生效,不用动虚拟机定义。
为什么不推荐硬编码 hostdev
| 对比 | 硬编码 domain XML | 在线 attach-device |
|---|---|---|
| VM 启动时设备不在场 | 启动失败(hostdev 缺失阻止域启动) | 无影响,随时补插 |
| 换设备 | 要改 domain XML(可能需关机) | 改设备 XML,在线生效 |
| VM 重启后 | 自动生效 | 需重新 attach |
硬编码方案唯一的好处是 VM 重启后自动挂载,代价是设备缺失时起不来、换设备要改定义——对「设备是可选的外设」场景不划算。
拔线之后会怎样(实测,推翻想当然)
实测环境:QEMU 10.0.8 + libvirt 11.3.0(Debian 13)。结论:物理拔线不会通知客户机。
- 物理拔线:客户机内
udevadm monitor收不到任何事件,设备在客户机里保持「插入」状态——僵尸设备,访问才报错,但客户机认为它还在。QEMU 的usb-host后端不会把宿主的物理拔出传播到客户机虚拟总线,libvirt(即使managed='yes')也不会自动 detach; virsh detach-device:客户机才走正常 USB 断开流程——udev 收到 REMOVE 事件、/dev/bus/usb/xxx节点删除、客户机 adb server 报 device disconnected(detach 向 QEMU 发device_del,这才是客户机断开事件的唯一来源)。
所以正确姿势:拔线/换线后手动 detach-device 清理,不然客户机一直挂着僵尸设备:
virsh detach-device debian13 usb-device.xml # 物理拔线后仍需显式 detach
再插回来:同样没有自动重挂——每次拔插、VM 重启后都要手动 attach-device。建议封装成一条链:
virsh start <vm> && virsh attach-device <vm> usb-device.xml
前置:普通用户操作 virsh
非 root 用户连 system libvirtd 需要授权。Debian 自带 polkit 规则 /usr/share/polkit-1/rules.d/60-libvirt.rules:libvirt 组成员免密放行 org.libvirt.unix.manage。所以只需:
sudo usermod -aG libvirt peter # 加入 libvirt 组
- 组权限对新会话生效,当前 shell 临时验证用
sg libvirt -c 'virsh list --all'; - 非 root 下
virsh默认连qemu:///session(看不到系统域),显式virsh -c qemu:///system或设export LIBVIRT_DEFAULT_URI=qemu:///system; - SSH 无 polkit 代理的场景也直接放行(规则直接返回 YES,不弹认证),适合脚本化。
总结
- 在线方案 =
attach-device(设备 XML 独立)+ vendor/product 匹配,换设备、拔插都不动虚拟机定义; - 硬编码 hostdev 的坑:设备不在场 VM 启动失败、换设备要改定义;
- 物理拔线不会通知客户机(实测):客户机挂着僵尸设备,需显式
detach-device才触发客户机 udev REMOVE; - 热插拔不持久,拔插、重启后都要手动 attach——脚本化一条链即可。