QEMU中的Virtio features协商流程
下面以virtio-net对接vhost-net(kernel)为例,分析整个Virtio features协商的过程。
vhost-net
vhost_net作为描述后端的数据结构,与真正使用的后端(vhost_user or vhost_net)对应。
每个vhost_net包含一个vhost_dev,vhost_net的features实际就是vhost_dev的features,与features相关的成员有:features
,acked_features
,backend_features
和protocol_features
:
1 | struct vhost_net { |
vhost_dev::features
在vhost_dev_init
中,通过后端对应的接口(ioctl or unix socket),从后端获取到的后端支持的features。
vhost_dev::acked_features
前端依据从后端获取的host features进行协商,协商完成后真正向后端传递的features(过滤了后端不支持的features),即后端真正工作的features。
vhost_dev::backend_features
从命名看是描述后端的features,但和features字段重复。vhost-net相关的实际代码调用如下:
- vhost_net_ack_features: 在设置acked_features之前,用backend_features的值为acked_features初始化。这里acked_features为何不初始化为0,从QEMU开发者的邮件讨论看,之前在初始化0时,acked_features在在某些情况下会被初始化为unexpected value(This will result an unexpected value of acked_features which may fail the features setting of vhost.)。不过初始化为backend_features似乎在vdpa的场景下有新的问题,且问题仍然open中;^1
- vhost_net_init: 在后端为vhost-net的条件下,如果后端对应的tap设备不支持vnet_hdr,则backend_features中的
VHOST_NET_F_VIRTIO_NET_HDR
被置位,否则为0; - vhost_user_backend_init: 在后端为vhost-user的条件下,如果后端支持
VHOST_USER_F_PROTOCOL_FEATURES
,则backend_features中的VHOST_USER_F_PROTOCOL_FEATURES
被置位。
此外,内核vhost代码中定义了VHOST_NET_BACKEND_FEATURES
,对应的ioctl操作为VHOST_GET_BACKEND_FEATURES
和VHOST_SET_BACKEND_FEATURES
,不过目前QEMU代码中没有这两个ioctl操作对应的逻辑。
vhost_dev::protocol_features
协议的扩展features。为了保持向下兼容性,vhost-user协议新增的features使用protocol_features进行描述,由VHOST_USER_F_PROTOCOL_FEATURES
进行控制,标识protocol_features是否使用。
1 |
在vhost-net场景下,protocol_features被设置为0。
virtio-net
virtio-net作为描述后端的数据结构,与Guest的前端驱动(virtio_net)对应。在QEMU中,用VirtIONet来描述virtio-net,每个VirtIONet结构中包含了其对应的VirtIODevice。其中与features有关的成员有:VirtIONet的host_features
,VirtIODevice的guest_features
,host_features
和backend_features
。
1 | struct VirtIONet { |
VirtIONet::host_features
从virtio设备的角度来看,只区分Guest与Host,并不关心Host的具体实现。所以VirtIONet::host_features代表了Host理论可以支持的所有features。
VirtIONet::host_features在virtio_net_class_init
中使用QEMU中virtio-net设备的默认属性virtio_net_properties
(其中包含了offload相关的features)来初始化,同时在每个virtio-net设备实例化时,依据后端设备(e.g. tap)的配置,对部分features进行设置。
VirtIONet::host_features唯一的调用处在virtio_net_get_features
,即响应Guest前端驱动的get_features请求,VirtIONet::host_features作为初始值,在一系列的过滤动作后,向前端返回Host所支持的features(细节见下文)。
VirtIODevice::host_features
VirtIODevice::host_features在virtio_bus_device_plugged
中调用对应virtio设备的get_features进行初始化,保存的是get_features的返回值,即真正返回给前端驱动的features。
virtio_net_get_features
向前端返回features的逻辑如下:
- VirtIONet::host_features作为初始features;
- 如果后端设备(e.g. tap)不支持vnet_hdr,则将features中对应的offload features取消置位;
- QEMU内部预定义了后端支持的feature_bits(kernel_feature_bits or user_feature_bits),将feature_bits支持但vhost_dev::features中不支持的bit在features中取消置位;
- 对
VIRTIO_NET_F_MTU
进行特殊处理(和mtu_bypass_backend
有关,对整体协商流程不重要); - 将上述流程中处理后的features返回给前端驱动。
要注意VirtIODevice::host_features和VirtIONet::host_features的区别,VirtIONet::host_features是Host理论可以支持的所有features,而VirtIODevice::host_features是对VirtIONet::host_features中依据后端、设备的设置进行处理后,真正返回给前端驱动的features。
VirtIODevice::guest_features
Guest前端驱动用自身features和get_features得到的VirtIODevice::host_features进行协商后,传递给QEMU,与VirtIODevice::host_features取交集,保存得到guest_features。即前后端在Guest协商后得到的features。
VirtIODevice::backend_features
看代码调用是为了特殊处理mtu_bypass_backend
保存的,在virtio_net_get_features
处理流程中,特殊处理VIRTIO_NET_F_MTU
前保存的features,除了VIRTIO_NET_F_MTU
和VirtIODevice::host_features有差异外,其他一致。
QEMU正常启动流程中的features协商
QEMU正常启动流程中,从开始运行到GuestOS启动的RunState的变化:preconfig
->prelaunch
->running
。
上述RunState变化过程中,virtio设备的features相关流程如下:
- preconfig:
qemu_init
->net_init_clients
->……->net_init_tap
->……->vhost_net_init
,初始化tap和vhost_dev。其中vhost_dev初始化时,调用后端对应的接口,获取后端支持的所有features,用来初始化vhost_dev::features
,同时依据对端设备(tap)支持的特性,初始化vhost_dev::backend_features
,以及初始化vhost_dev::protocol_features
为0; - prelaunch: 调用
virtio_bus_device_plugged
,通过对应设备类型的get_features,初始化VirtIODevice::host_features
; - running:
- GuestOS启动,内部virtio_net驱动被加载,调用
virtio_dev_probe
->……->vp_get_features
获取设备的features,即QEMU侧的VirtIODevice::host_features
,再与自身驱动支持的features进行协商,协商完成后调用virtio_finalize_features
->……->vp_set_features
将协商好的features传给QEMU; - 此时发生VM-EXIT,QEMU侧调用
virtio_set_features
->……->virtio_net_set_features
->……->vhost_ack_features
,把前端驱动传递过来的features和后端对应feature_bits共同支持的bit置位,将结果保存在vhost_dev::acked_features
; - GuestOS中
virtio_finalize_features
->……->vp_set_features
执行完成后,会设置VIRTIO_CONFIG_S_FEATURES_OK
,进而触发QEMU侧的对应调用virtio_set_status
->……->vhost_dev_start
。在vhost_dev_start
中,调用vhost_dev_set_features
,将前端协商好的vhost_dev::acked_features
通过后端对应的接口传递到后端。
- GuestOS启动,内部virtio_net驱动被加载,调用
QEMU热迁移流程中的features协商
QEMU热迁移流程中,源虚拟机从开始运行到迁移结束的RunState变化:running
->finish_migrate
->postmigrate
;目的虚拟机从开始运行到GuestOS启动的RunState的变化:preconfig
->inmigrate
->running
。
源端virtio features在热迁移流程中的变化
- running: 正常运行状态,无相关操作;
- finish_migrate:
virtio_save
中保存VirtIODevice::guest_features
的低32bit,迁移到目的端。
目的端virtio features在热迁移流程中的变化
- preconfig: 和正常启动流程一致;
- inmigrate:
- 调用
virtio_bus_device_plugged
,通过对应设备类型的get_features,初始化VirtIODevice::host_features
; virtio_load
->……->virtio_net_set_features
->……->vhost_ack_features
,在virtio_load
中接收源端协商完成的VirtIODevice::guest_features
,在vhost_ack_features
中将接收到的VirtIODevice::guest_features
和后端对应feature_bits共同支持的bit置位,将结果保存在vhost_dev::acked_features
;
- 调用
- running:
qemu_main_loop
->main_loop_wait
->……->vm_start
->……->virtio_set_features
,使用inmigrate过程中设置好的vhost_net::acked_features
通过后端对应的接口传递到后端。
正常启动流程和热迁移流程对比
硬件设备的初始化流程都一致,除了PCI设备的一些状态也是通过迁移初始化之外,最主要的区别在于热迁移流程中的features协商过程:协商必须的VirtIODevice::guest_features
通过热迁移从源端获取,后端直接和QEMU进行协商流程;协商流程控制不再需要前端驱动修改设备status,而是在QEMU热迁移流程中触发。前端驱动不参与、不感知,避免了重新初始化。