本文主要介绍通过 virsh detach-device 命令发起卸载虚机磁盘设备操作时 qemu 的处理流程(x86_64架构+KVM),分别以 virtio-blk 盘和 virtio-scsi 盘为例进行分析,并简要说明这两类磁盘热拔时的区别。此外,也顺带介绍下虚机内部移除磁盘时 qemu 侧的处理流程。
如有不对之处,烦请指正,感谢。
环境信息
- ARCH:x86_64
- Host OS:Linux 6.6.0-98.0.0.103.oe2403sp2.x86_64 | openEuler 24.03 (LTS-SP2)
- Guest OS:Linux guest 6.6.0-102.0.0.8.oe2509.x86_64 | openEuler 25.09
- QEMU:QEMU emulator version 10.2.0 (v10.2.0) | stable-10.2
- Libvirt:libvirtd (libvirt) 9.10.0
其中 QEMU 为社区 stable-10.2 版本,自行编译安装,Libvirt 为 9.10.0 版本,直接从欧拉默认源安装。
1、虚机创建命令和测试磁盘创建命令如下:
# guest
virt-install --name test --vcpus 2 --ram 2048 --import --disk path=/var/lib/libvirt/images/openEuler-25.09-x86_64.qcow2,format=qcow2 --network network=default --graphics vnc,listen=0.0.0.0 --noautoconsole --osinfo openeuler22.03
# disk
qemu-img create -f qcow2 /var/lib/libvirt/images/test-disk.qcow2 1G
2、virtio-blk 盘 xml 信息如下:
# disk.xml
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' />
<source file='/var/lib/libvirt/images/test-disk.qcow2'/>
<target dev='vdb' bus='virtio'/>"
</disk>
3、virtio-scsi 盘 xml 信息如下:
# disk-scsi.xml
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' />
<source file='/var/lib/libvirt/images/test-disk.qcow2'/>
<target dev='sdb' bus='scsi'/>"
</disk>
注:
1、通过 virsh attach-device test disk.xml 命令挂载后,再使用 virsh detach-device test disk.xml 命令卸载,默认为 --live 即热插拔模式。
2、libvirt 通过调用 qmp device_del 命令进行设备热卸载。
3、如果通过 virsh attach-device 命令挂载 virtio-scsi 盘显示挂载成功,但是虚机内部不识别,可能是虚机 xml 未配置 virtio-scsi controller,需要在虚机 xml 中添加 <controller type='scsi' model='virtio-scsi'/>。
virtio-blk 盘热拔流程
先看热拔 virtio-blk 盘 qemu 侧处理流程。
1、IO 线程处理
qemu 进程 io 线程(mon_iothread)监听到 qmp 事件,经过一系列的流程后会调用 handle_qmp_command 处理,将对应请求加入协程队列(g_queue_push_tail(mon->qmp_requests, req_obj))并唤醒(aio_co_wake(qmp_dispatcher_co))。
2、主线程处理
1、主线程中完成协程处理,流程为monitor_qmp_dispatcher_co --> monitor_qmp_dispatch --> qmp_dispatch,组装好 bh 后调用 aio_bh_schedule_oneshot 将 do_qmp_dispatch_bh 加入 aio 队列(aio_bh_schedule_oneshot(qemu_get_aio_context(), do_qmp_dispatch_bh, &data));
2、主线程轮询到 aio 事件,调用 do_qmp_dispatch_bh 处理,对应回调函数为 qmp_marshal_device_del,实际处理函数为qmp_device_del;
3、 qdev_unplug 会调用hotplug_handler_unplug_request 进行处理,对应hdc->unplug_request 为piix4_device_unplug_request_cb;
4、piix4_device_unplug_request_cb 中会调用acpi_pcihp_device_unplug_request_cb,然后调用acpi_send_event向虚机发送卸载设备 ACPI 中断事件,对应 adevc->send_event 为piix4_send_gpe;
5、piix4_send_gpe中经过一系列处理,最终调用 kvm_set_irq 把中断注入虚机(kvm_vm_ioctl(s, s->irq_set_ioctl, &event))。
3、虚机系统处理
中断注入虚机后,虚机操作系统调用相应的驱动程序进行处理,处理完成后通过 ACPI BIOS _EJ0 方法 请求磁盘设备删除。
4、KVM 线程处理
虚机内部处理完成后发起的移除设备请求由 kvm 线程处理,最终调用 qdev_unrealize 完成 qemu 侧磁盘设备移除。
注意,此时虚机内部发起设备移除请求对应的处理函数是 hotplug_handler_unplug,而前面 qmp 命令对应的处理函数是hotplug_handler_unplug_request。
virtio-scsi 盘热拔流程
再看下热拔 virtio-scsi 盘 qemu 侧处理流程。
1、IO 线程处理
与 virtio-blk 盘一样,io 线程先调用 handle_qmp_command 处理。
2、主线程处理
1、与 virtio-blk 盘一样,主线程调用 qmp_device_del 进行处理;
2、与 virtio-blk 盘不一样,在 qdev_unplug 中,由于 hdc->unplug_request 为空,所以是调用 hotplug_handler_unplug,而不是hotplug_handler_unplug_request,相当于已经走到了热拔 virtio-blk 盘时虚机操作系统处理完后 kvm 线程的处理流程;
3、在 hotplug_handler_unplug 中,对应的 hdc->unplug 是 virtio_scsi_hotunplug;
4、virtio_scsi_hotunplug 会调用 qdev_simple_device_unplug_cb,而 qdev_simple_device_unplug_cb 则直接调用 qdev_unrealize 完成 qemu 侧磁盘设备移除。
5、virtio_scsi_hotunplug 中再调用 virtio_scsi_push_event 通知虚机磁盘设备已移除。
virtio-blk 盘和 virtio-scsi 盘热拔区别
综上,virtio-blk 盘和 virtio-scsi 盘在热拔时的明显区别是:热拔 virtio-blk 盘需要等待虚机操作系统内部驱动响应,而热拔 virtio-scsi 盘无需等待虚机响应。因此,在虚机内部无响应时热拔 virtio-blk 盘会超时甚至失败。
此外,QEMU 官方博客中也提到 virtio-blk 盘相比 virtio-scsi 盘性能更好(virtio-scsi更加复杂,IO路径更深),virtio-scsi 则支持挂载超过 28 块盘以及完整的 SCSI 支持。
- Prefer virtio-blk in performance-critical use cases.
- Prefer virtio-scsi for attaching more than 28 disks or for full SCSI support.
虚机内部移除磁盘
现在看下在虚机内部热拔磁盘时 qemu 侧的处理,还是分别以 virtio-blk 盘和 virtio-scsi 盘为例,直接在 qdev_unrealize 处打上断点。
注:虚机内部通过执行echo 0 > /sys/bus/pci/slots/xx/power 命令移除设备。
virtio-blk 盘移除
gdb 结果如下所示,可以发现,虚机内直接通过 ACPI BIOS _EJ0 方法请求设备删除,由 kvm 线程处理对应 io 事件,对应流程为pci_write -> acpi_pcihp_eject_slot -> acpi_pcihp_device_unplug_cb -> qdev_unrealize。这和通过 qmp 命令热拔 virtio-blk 盘时虚机操作系统处理完后 kvm 线程的处理流程一致。
virtio-scsi 盘移除
gdb 结果如下所示,可以发现,依然是虚机内直接通过 ACPI BIOS _EJ0 方法请求设备删除,由 kvm 线程处理对应 io 事件。
最后调用 qapi_event_send_device_deleted 通知 libvirt 设备已移除。
注:
1、virtio-blk:每“加一块盘”,就等于“加一块 PCI 设备”,所以 lspci 会多一项;virtio-scsi:只在最初“加一块 SCSI 控制器”,之后加的是 LUN(逻辑单元),不是 PCI 设备,所以 lspci 不会变。
2、虚机内移除 virtio-scsi 盘时是把 SCSI 控制器 整个 PCI 设备移除了,所以/machine/peripheral/scsi0-0-0-1、/machine/peripheral/scsi0/virtio-backend和/machine/peripheral/scsi0 都移除了,通过 qmp 命令热拔 virtio-scsi 盘只是把盘移除,scsi 控制器还是在的。
总结
1、通过 qmp 命令热拔磁盘时,virtio-blk 盘热拔需要等待虚机内部响应并发起移除请求后 qemu 侧才删除设备,virtio-scsi 盘热拔则是 qemu 侧先删除设备再通知虚机设备已移除。因此热拔 virtio-blk 盘在虚机内部未响应时可能会卡住或失败;
2、从虚机内部移除磁盘时,virtio-blk 盘和 virtio-scsi 盘的行为基本一致,qemu 侧直接删除即可;
3、Windows 虚机里“安全删除硬件”(非设备管理器中卸载设备)相当于 Linux 虚机中执行 echo 0 > /sys/bus/pci/slots/xx/power 命令移除设备,都是通过 ACPI BIOS _EJ0 方法 请求设备删除,qemu 侧能感知到相关请求。
以下为 ChatGPT 基于文章内容生成的 plantUML 流程图,非常清晰。
参考资料
1、QEMU<->ACPI BIOS PCI hotplug interface
2、Configuring virtio-blk and virtio-scsi Devices
3、QEMU-KVM设备热插拔机制简析
4、block中断 virtio_vhost-user 关于 resize block device 的支持
5、virtio-blk vs virtio-scsi