用户态虚拟化IO通道实现概览及实践(上)

2023-11-04

自虚拟化技术诞生起,提升虚拟化场景中IO设备性能和驱动的兼容性、可扩展性一直是备受关注和追求的目标。随着半虚拟化技术的出现,virtio设备及驱动也很快流行并逐步变成了虚拟化应用中的主要IO通道形态。例如,virtio现已支持实现的设备涵盖了网络设备(virtio-net)、块设备(virtio-blk)、串口设备在(virtio-console)等等。其后,通过DPDK/SPDK技术加持,virtio技术全用户态化的实现也如火如荼地落地并广泛应用。

不仅如此,沿着虚拟IO全用户态化的思路,vfio-user技术也应运而生,且被SPDK集成用于为虚拟机提供了高性能模拟的NVMe设备。

 接下来,本文将主要结合virtio和vfio-user的角度来对主要的虚拟化IO通道的用户态实现方式进行简单的介绍。

virtio的

用户态实现分析

对于virtio的用户态实现技术,此处将主要从virtio标准、virtio front-end和back-end的用户态实现情况分别进行说明。

virtio标准 

virtio机制让客户机(Guest)系统知道自己是运行在虚拟化环境中,通过与hypervisor配合协作,借助CPU提供的虚拟化技术(如Intel VT-d或AMD-v)达到更好的性能。同时,virtio机制也给众多的虚拟化平台和系统提供了统一的IO设备及驱动模型,提升了多平台&多系统扩展性、兼容性,降低了跨系统移植复用时的复杂度。

virtio的机制从2008年在论文中提出后,已经过了v0.95、v1.0 和v1.1 这3个标准。其主要构成可以大致分为前端(front-end)驱动、传输队列(virtqueue)和后端(back-end) 设备这几个部分。Front-end驱动运行在虚拟机(Guest)系统或者bare-metal应用进程中,back-end设备存在于主机(Host)系统的hypervisor进程(如Qemu)或者单独的进程(或模块)中,而virtqueue则主要通过内存共享映射在front-end驱动和back-end设备之间传递数据。virtqueue的主要实现结构是vring,在virtio的的各个标准版本中有split和packed两种实现方式。

05194d22531703e2df92e99ea3f28a38.png

图1. Virtio机制主要结构

(transport即指virtqueue,通过virng实现)

virtio的back-end主要实现

为了尽可能的提升性能,virtio back-end设备的实现方式一直在不断变化。以KVM-Qemu应用场景为例,back-end设备的实现就从最初完全在Qemu进程中逐步变为“控制面通过Qemu、IO面基于独立进程或模块(如vhost)”以及“控制面基于Qemu、IO面基于硬件(vDPA)”的形式。

完全基于Qemu的实现

在back-end设备完全基于Qemu的实现方式中,virtqueue back-end侧的处理在Qemu进程中完成,涉及的数据由Qemu进程解析后再和实际进行数据处理的设备驱动交互。虽然这种方式使用过程中Qemu进程可以不依赖于其他的进程或模块,但这的确也是性能最差的virtio back-end设备实现方式。

798767a160c5d7047a93377f167e8c1b.png

图2. 完全基于Qemu的virtio实现举例

(IO路径如见虚线)

Vhost实现

vhost实现就是将virtqueue的IO面(数据处理逻辑)从Qemu进程中拿出来由Host下的其他模块或者进程来实现,控制面(如virtqueue的参数协商等)仍需经过Qemu进程完成。得益于CPU的虚拟化技术,可以实现Host系统和Guest系统下内存地址的直接映射和快速转换,从而提升了IO访问的性能。

vhost的IO处理逻辑最初是在内核态实现,Qemu通过ioctl的机制和内核态vhost模块进行交互。随着后续的发展,逐步有了基于用户态进程的实现 --- Qemu直接在用户态通过unix socket与vhost进程进行交互 。

对基于用户态实现的vhost,其既可以通过Qemu等hypervisor向Guest系统呈现pci形态的virtio设备来与运行在Guest下的virtio front-end驱动交互;也能够单独以user通道(基于unix socket)的方式来向其他进程(例如,与Qemu无关的Container等 bare-metal模式应用)提供virtio back-end设备功能。对于第二种情况,其对应的virtio front-end 驱动也是基于virtio-user类型的实现。

8a2f53996af22f0ef947f683e470ea1b.png

图3. vhost back-end实现示例

vDPA实现

vDPA(virtual Data Path Acceleration)就是在vhost的思路上更进一步,将back-end侧对virtqueue的数据处理逻辑完全由硬件来实现。这样可以进一步降低Host侧CPU的消耗,从而提升性能。不仅如此,这种实现方式还带来了其他的价值。vDPA的实现和SR-IOV应用时的pci pass-through的方式有点类似(不过SR-IOV方式下控制面和数据面都卸载到硬件来执行)。但相较于pci pass-through模式在Guest系统下应用时不同设备需要各自不同驱动来支持以及无法实现VM在线迁移(live migration)的限制,vDPA使得在Guest系统下使用统一的front-end驱动来支持不同的设备以及实现在线迁移特性成为可能。

virtio用户态virtio front-end驱动\back-end设备实现

鉴于DPDK/SPDK用户态驱动在性能优化方面的独到特性,virtio front-end驱动和back-end设备的用户态版本,主要基于DPDK/SPDK框架来进行开发。接下来本文将从front-end驱动和back-end设备的角度来分别介绍virtio的用户态实现及其主要的代码结构和运行机制。

virtio front-end 驱动的用户态实现

virtio front-end 驱动主要工作于Guest系统中,对基于KVM-Qemu的VM系统,其主要的处理对象是Qemu虚拟出来的virtio类型的pci controller;对于bare-metal模式的其他系统,其可以通过用户态unix socket来与后端设备交互。目前主流的virtio front-end用户态驱动主要有:virtio-net、virtio-blk/virtio-scsi、virtio-user-net、virtio-user-blk/virtio-user-scsi这几种。

virtio-net的用户态驱动

virtio-net用户态驱动的实现代码在 “DPDK/drivers/net/virtio” 目录下,其主要处理对象是 “Virtio network device” 的pci controller。结合代码可以大致将相关流程梳理如下:

1. DPDK程序启动时,注册支持的设备id-table 和驱动函数;

1)设备id-table通过“pci_id_virtio_map”数据结构定义了支持的pci vendor和pci device的ID并使用RTE_PMD_REGISTER_PCI_TABLE(net_virtio, pci_id_virtio_map)进行注册;

2)驱动由“rte_virtio_net_pci_pmd”数据结构定义,并在 “rte_pci_register(&rte_virtio_net_pci_pmd) ”中进行注册。

2. 在DPDK初始化流程中完成设备探测匹配并加载驱动;

在DPDK初始化流程中主要藉由DPDK eal提供的用户态bus、device、driver的匹配模型进行设备探测、匹配和驱动加载。函数rte_eal_init会根据DPDK程序启动参数中指定的允许访问的设备信息进行设备探测和匹配(默认情况下DPDK/SPDK均不会主动加载通过vfio、uio驱动管理的设备,只会在参数显示指定后进行处理) 。当匹配到前述已注册的virtio-net的设备后,就会回调加载对应注册的驱动并调用eth_virtio_pci_probe进行设备初始化。

3. 初始化virtio network device pci controller;

初始化的入口函数是eth_virtio_pci_probe,除基本的pci相关的初始化外,其中主要可以分为两个层面的资源处理,其一是virtio设备本身层面的,如在vtpci_init函数中根据设备类型填充vtpci_ops类型的函数集,用于执行virtio设备的基本配置操作;其二,是rte_eth_dev设备层面的,在函数eth_virtio_dev_init中指定virtio-net设备关于网络特性相关的eth_dev_ops函数集,用于支持rte_eth_dev的特性操作。需要注意的是在此处也会在virtio_init_device函数中根据配置创建virtqueue并与硬件同步。

4. DPDK应用程序使用virtio-net设备相关的函数接口进行数据收发;

在DPDK应用程序中执行rte_eth_dev_start操作,设置rx\tx 处理函数、中断处理函数,并将virtqueue的状态通知给back-end设备。在这之后DPDK应用程序就可以通过rte_eth_tx_burst和rte_eth_rx_burst来执行数据的发送和接收。

在DPDK应用程序启动时通过-a指定“Virtio network device”的pci controller的bdf地址,virtio-net的用户态驱动会被匹配并加载。在该过程中会有如“EAL: Probe PCI driver: net_virtio (1af4:1000) device: 0000:00:04.0 (socket 0)”的打印信息,标识virtio-net驱动已初始化了virtio network device并将其注册成为了一个DPDK框架下的eth 端口,之后可以通过RTE_ETH_FOREACH_DEV类似的函数来查找并获取对应的port并使用。

virtio-blk/virtio-scsi的用户态驱动

virtio-blk和virtio-scsi的用户态驱动代码在 “SPDK/module/bdev/virtio” 目录中,其主要处理对象是 “Virtio block device” 和 “Virtio SCSI” 的pci controller,将virtio pci controller初始化成spdk中的spdk_bdev设备对外提供服务。virtio-blk和virtio-scsi的设备的代码流程和前述的virtio-net的驱动流程类似。具体以virtio-blk的流程梳理概述如下:

1. 根据rpc命令传递的参数匹配pci设备并进行pci设备基本初始化;

响应rpc bdev_virtio_attach_controller命令pci/blk类型操作的入口函数是bdev_virtio_pci_blk_dev_create。该函数执行中调用了spdk_pci_device_attach(其中主要是通过DPDK的rte_eal_hotplug_add接口来实现)来执行pci设备匹配。当给定pci地址的设备扫描添加成功后,会回调指定的回调函数virtio_pci_dev_probe来完成设备合法性判断、bar空间映射、基本操作函数集添加等公共初始化步骤。

2. 创建virtio_blk描述设备,并完成virtio设备层级和blk层级的初始化;

在virtio_pci_dev_probe函数执行的末尾,会回调在bdev_virtio_pci_blk_dev_create中设置的回调函数 bdev_virtio_pci_blk_dev_create_cb,并在其中完成virtio_blk设备的创建和初始化。这个初始化的过程也基本可以分成两个部分:

1) 在virtio_pci_dev_init中完成virtio设备的基本初始化,如feature协商、virtio设备基本操作函数集(如modern_ops)添加、virtqueue创建及与back-end设备同步等;

2)  在virtio_blk_dev_init中创建并注册bdev到SPDK框架,让virtio设备对外呈现为bdev提供服务。其中指定的bdev的操作函数集virtio_fn_table可以响应SPDK应用层的读写及配置命令与back-end设备交互。

3. SPDK应用程序通过bdev块设备层的访问接口对块设备进行IO及控制类操作。

SPDK应用主要通过呈现的bdev对virtio 设备进行访问,IO读写及控制类接口与其它普通bdev无异,其后台由前述的virtio_fn_table中的函数来进行转化处理。

需要注意的是,在上述流程中,会以virtio_blk描述结构为io_device通过spdk_io_device_register函数进行注册,以便当SPDK应用程序执行IO操作时能够通过设置的回调函数bdev_virtio_blk_ch_create_cb,在各个使用的CPU核上将bdev的io_channel和virtqueue(vring)关联起来。

同时,在BSC(Big Spring Canyon , 一种基于FPGA实现的Intel Smart NIC)上实现的blk-net的设备本质上也是virtio_blk设备的一种特殊形态,其代码逻辑和上述描述的过程大体对应。

对于virtio-blk的用户态front-end驱动的使用,可以通过SPDK的test程序bdevperf来进行。比如可以通过json配置文件或者rpc命令指定要使用的virtio block device的pci addr信息及对应创建的spdk_bdev设备名称,然后对该bdev设备跑测试。具体的使用在本文后续章节会进行描述。

virtio-user的用户态驱动

virtio-user模式的front-end驱动,本质上是为了在抛开Qemu等hypervisor的场景下和用户态实现的vhost back-end设备进行对接而实现的。其主要的使用场景可参考在bare-metal模式下的相关应用,或者针对vhost设备的简单测试需求。目前在DPDK/SPDK代码中所存在的virtio-user驱动的实现主要是两类,其一是DPDK中的virtio-net-user驱动,其二是SPDK中的virtio-blk-user/virtio-scsi-user驱动。

virtio-user模式的front-end驱动主要是通过unix socket来与back-end 的设备进行控制信息的协商和交互,并且作为client的角色。通信初始时会根据与vhost侧约定的文件来创建unix socket并基于此和vhost server进行链接。其二者之间的数据较互也通过内存共享映射来完成。

virtio-net-user驱动

virtio-net-user驱动代码存在于 “DPDK/drivers/net/virtio/virtio-user” 目录下。其计划支持对接的back-end的设备类型有:vhost-user、vhost-kernel、vhost-vdpa,此处仅结合vhost-user类型的back-end设备来进行介绍。结合DPDK代码中的实现,可以大致概述其相关的流程如下:

1. 在rte_eal_init函数中通过传入的参数扫描并匹配对应virtio 类型的rte_vdev设备; 

1)virtio-net-user驱动作为一种rte_vdev_bus总线设备的驱动以RTE_PMD_REGISTER_VDEV(net_virtio_user, virtio_user_driver)的方式注册到了DPDK的vdev驱动链表中;

2)当ret_eal_init函数执行时,其中会执行ret_bus_scan和rte_bus_probe的操作,若DPDK的启动参数中指定了rte_vdev设备时,就会在rte_vdev_bus总线类型设备的probe操作中对从vdev_device_list获取的设备依次从vdev_driver_list中查找到相对应的驱动并进行加载。对于net_virtio_user类型的设备,该过程最终回调的驱动函数就是virtio_user_pmd_probe。 

2.调用注册的virtio_user驱动完成相关初始化并对外提供为通用的用户态网络设备;

在virtio_user_pmd_probe函数中,初始化操作的类型大致也可以概述为两个方面:其一,virtio设备层面的初始化,例如指定virtio的基本操作函数集virtio_user_ops,在virtio_user_dev_init函数中初始化virtio_user_dev的virtio_user_backend_ops类型指针,与back-end设备建立链接等;其二,在eth_virtio_dev_init函数中初始化rte_eth_dev类型的设备,如设置其网络特性的操作函数集virtio_eth_dev_ops,初始化virtqueue等。 

3.DPDK应用程序通过用户态网络设备访问的函数接口完成数据的收发操作;

在DPDK应用程序中执行rte_eth_dev_start操作,设置rx\tx 处理函数、中断处理函数,并将virtqueue的状态通知给back-end设备。在这之后DPDK应用程序就可以通过rte_eth_tx_burst和rte_eth_rx_burst来执行数据的发送和接收。

virtio-net-user用户态驱动可以通过testpmd程序来进行测试,由--vdev参数指定vdev驱动的名字(形如net_virtio_user,或者net_virtio_user*,用于匹配virtio-net-user驱动)和用于链接用户态back-end设备(vhost-net)的socket句柄文件(形如path=/var/tmp/socket0)。 

virtio-blk-user/virtio-scsi-user驱动

virtio-blk-user/virtio-scsi-user的驱动代码存在于 “SPDK/module/bdev/virtio” 目录中。也即在SPDK代码中,处理pci controller和直接对接vhost-user的virtio front-end 驱动的实现是在一起的,以不同的分支存在。若以virtio-blk-user驱动为例,可以将其相关流程概述如下:

1. 根据rpc命令传递的参数创建virtio_blk_dev并进行初始化;

响应rpc bdev_virtio_attach_controller命令user/blk类型操作的入口函数是bdev_virtio_user_blk_dev_create。该函数执行中通过调用virtio_user_blk_dev_create来创建virtio_blk_dev并进行初始化。主要的初始化步骤由virtio_user_dev_init和virtio_blk_dev_init这两部分完成,前者执行virtio 相关的初始化如与back-end设备建立unix socket链接等,后者完成blk设备相关的初始化如创建并注册bdev设备。

1) virtio_user_dev_init函数中初始化了virtio back-end设备相关的操作函数集virtio_user_ops以及用户态驱动和back-end进行交互的virtio_user_backend_ops的函数集ops_user,并与back-end设备建立链接;

2)  virtio_blk_dev_init完成了virtqueue的初始化,创建并注册bdev到SPDK框架,让virtio设备对外呈现为bdev提供服务。其中指定的bdev的操作函数集virtio_fn_table可以响应SPDK应用层的读写及配置命令与back-end设备交互。

2. SPDK应用程序通过bdev块设备层的访问接口对块设备进行IO及控制类操作。

SPDK应用主要通过呈现的bdev对virtio 设备进行访问,IO读写及控制类接口与其它普通bdev无异,其后台由前述的virtio_fn_table中的函数来进行转化处理。

 virtio-blk-user/virtio-scsi-user驱动可以通过SPDK的test程序bdevperf来进行验证,其实用方式与前述pci controller模式的virtio-blk/virtio-scsi用户态驱动类似,只是注意指定-t的参数为user即可。具体步骤可以参考后续验证章节的例子描述。

virtio back-end 设备的用户态实现

virtio back-end设备用户态实现逻辑工作在Host系统的DPDK/SPDK进程中。当前支持的back-end设备类型主要有: vhost-net、vhost-blk/vhost-scsi和vshot-vdpa。在DPDK/SPDK进程中实现的virtio back-end设备一方面可以与Qemu进程配合,将实现的设备呈现为Guest系统下可见的pci controller供VM使用;另一方面也可以抛开Qemu直接和另外的用户态进程(如Container进程等)直接通过unix socket对接,为bare-metal应用提供服务,此时的back-end设备所在进程充当server的角色。

virtio back-end设备用户态实现的基本框架接口代码是在 “DPDK/lib/vhost”目录中,其主要的3个接口是:rte_vhost_driver_register、rte_vhost_driver_callback_register和rte_vhost_driver_start。对于上述几种不同的virtio back-end设备的用户态实现,都是在这个接口框架下来进行工作。这也意味着virtio back-end设备的各种类型实现(其中包括vdpa)都是以vhost的形态对外提供服务,或者都是在vhost 的接口框架下和front-end驱动进行交互。

在rte_vhost_driver_register函数中,根据指定的文件路径创建unix socket句柄,并设置其为server的角色。对于各个virtio back-end的各个类型设备,其均为server的角色。对于KVM-Qemu使用场景下的Qemu进程,其与vhost-user的back-end设备链接时,其角色为client。通过函数rte_vhost_driver_callback_register,各个不同的back-end设备可以设置各自的创建和销毁与一个front-end关联关系的notify_ops操作函数集。而rte_vhost_driver_start函数的执行则是启动vhost unix socket的监听,并设置请求处理的回调函数以实现在vhost_user_server_new_connection函数(其中可能会回调上述notify_ops中的new_connection函数)中来处理从front-end驱动发送过来的建链请求,且在其后以vhost_user_msg_handler来处理从链接上过来的消息(控制类消息)。

用户态virtio back-end设备实现中,与front-end驱动之间的virtqueue的vring对应的内存通过共享内存方式来实现,并且由virtio front-end驱动来分配,vhost back-end设备侧根据front-end发送过来的控制消息调用vhost_user_set_mem_table函数进行映射。

在vhost和vdpa的使用场景中,推荐VM配置中添加vIOMMU以达到更安全的使用目的。

vhost-net back-end设备

vhost-net的back-end设备实现逻辑在 “DPDK/examples/vhost” 和 “DPDK/driver/net/vhost” 目录下均有实现,前者是实现了vhost-switch的功能,可以为VM提供数据转发的功能;后者则是在DPDK框架中注册一个eth端口,其背后实际是一个vhost-net的设备,以实现像使用DPDK的普通eth设备(port)一样处理virtio的数据和报文---从该eth port发送的报文被放到vritqueue中由vhost-net对应的front-end驱动接收,该eth port从vhost-net的virtqueue中接收来自front-ned驱动发送过来的报文。此处以“DPDK/examples/vhost”的实现为例来概述vhost-net back-end设备的相关工作流程:

1.设置vhost  back-end处理函数并开启vhost server监听;

主要是根据用户传入的参数调用前述的“rte_vhost_driver_register、rte_vhost_driver_callback_register和rte_vhost_driver_start” 函数来完成。需要注意的是通过rte_vhost_driver_callback_register设置的virtio_net_device_ops函数集到vsocket->notify_ops中用于处理和front-end驱动关联时的相应操作。 

2.响应front-end驱动的建链请求建立back-end设备与front-end驱动的关联; 

当有virtio front-end驱动链接vhost back-end设备时,每个front-end对应的关联关系会在vhost_user_add_connection函数中创建一个virtio_net类型的数据结构进行标识。后续针对该virtio-front驱动的消息处理均以此数据结构归集和查找。并且针对初始发送过来的数据,在vhost_user_msg_handler会回调dev->notify_ops->new来进行具体back-end定义的私有化处理。在当前的流程中主要是进一步创建了vhost_dev来跟踪该链接关系,并将其添加到分配的CPU核对应的链表lcore_info[vdev->coreid].vdev_list中。

3.循环处理virtqueue中的数据;

该程序逻辑在每个可用的CPU核上创建了一个线程循环运行switch_worker函数,循环处理各个vhost_dev对应的数据收发。数据收发的过程可以分别简单归纳如下:

1)接收方向主要在drain_eth_rx函数中实现

A.通过rte_eth_rx_burst从指定的DPDK eth port收包; 

B.通过rte_vhost_enqueue_burst将数据放入virtqueue传给virtio front-end驱动。

2)发送方向主要在drain_virtio_tx函数中实现

A.先通过vs_dequeue_pkts从virtqueue中接收报文; 

B.再通过virtio_tx_route将数据放入其他front-end设备对应的virtqueue的接收buf或者通过指定的DPDK eth prot发送出去。

DPDK代码中的这个vhost-net back-end设备的测试程序可以通过编译DPDK时由 “-Dexamples=vhost” 来指定编译,并参考http://doc.dpdk.org/guides/sample_app_ug/vhost.html# 的说明来使用。

vhost-blk/vhost-scsi back-end设备

vhost-blk/vhost-scsi back-end设备的实现代码在 “SPDK/lib/vhost” 目录下。其主要是以spdk中可见的bdev作为实际载体,将其attach到vhost blk/scsi controller共同作为virtio back-end设备对外提供服务。以vhost-blk为例,可以将其实现流程基本概述如下:

1.响应rpc命令创建并初始化vhost_blk 设备;

SPDK中vhost-blk的功能以rpc方式显示调用,响应的函数为spdk_vhost_blk_construct。在其中,会创建spdk_vhost_blk_dev,并获取指定的spdk_bdev设备的访问句柄供后续响应、处理virtio front-end驱动的请求时使用。同时在在vhost_blk 设备 vhost_dev_register -> vhost_user_dev_register -> vhost_register_unix_socket的调用中执行前述的DPDK库提供的vhost的3个基本的初始化函数。

2.接收来自virtio front-end驱动的消息,建立front-end和back-end的关联; 

当virtio front-end驱动的建链请求被DPDK库中的函数处理后,vhost-blk侧即可以接收并处理控制消息。如前述,函数vhost_user_msg_handler在处理接收到的消息时,第一次当对应的front-end的virtio_net设备(vhost-blk基于DPDK的vhost框架实现,在DPDK库中back-end 设备与每个front-end驱动的链接对应一个virtio_net的数据结构,并以此跟踪标识后续的消息)还未置位VIRTIO_DEV_RUNNING标志,则会执行vhost_user_msg_handler -> start_device -> vhost_blk_start -> vhost_blk_start_cb的调用流程的调用流程,并在其中分配用于IO处理的task_pool及注册poller用于处理virtqueue中的数据(poller的处理函数为vdev_worker)。该过程执行完成后即会置位VIRTIO_DEV_RUNNING标志。

3.在注册的poller函数中处理virtqueue的队列数据;

对于每个从virtqueue的vring中取出的IO数据会被归集在spdk_vhost_blk_task数据结构中,由函数spdk_vhost_blk_task提交到vhost-blk关联的spdk bdev设备进行处理。

SPDK代码中,vhost的app程序由 “SPDK/app/vhost” 目录下的文件编译,默认即会编译。其使用参考见https://spdk.io/doc/vhost.html。

在DPDK代码 “DPDK/examples/vhost-blk” 目录下,同样有一个vhost-blk的逻辑实现,其工作流程与SPDK中的vhost-blk的基本一致,主要的区别点在于在后端实际处理IO数据的对应bdev设备实现时没有经过SPDK bdev的框架,且目前代码中仅实现了一种通过申请的内存模拟的bdev类型。DPDK代码中vhost-blk的测试程序可以通过meson配置编译时加上“-Dexpamples=vhost_blk”参数来生成,使用说明可以参考http://doc.dpdk.org/guides/sample_app_ug/vhost_blk.html。

vhost-vdpa back-end设备

vhost-vdpa back-end设备的实现流程大体与vhost-net/vhost-blk/vhost-scsi一致。其主要差别在于:其一,支持vdpa的设备驱动通过rte_vdpa_register_device函数注册了rte_device和支持的功能接口到vdpa_device_list链表中,如此应用程序可以通过rte_vhost_driver_attach_vdpa_device将其赋给vhost_user_socket并最终与标识vhost设备的virtio_net数据结构关联;其二,vdpa的vritqueue的处理均由硬件完成,因而不需要实现函数来周期性处理virtqueue中的数据。当前vhost-vdpa的实现都放在DPDK代码中,且初步的后续计划也是在DPDK中进行支持。vhost-vdpa的功能涉及的代码主要是两个部分,其一是vhost基本机制的代码,即前以提到的“DPDK/lib/vhost” 目录下的代码;其二是vdpa设备驱动,主要在“DPDK/dirvers/vdpa” 目录下,该部分代码实现的功能主要通过上述的rte_vdpa_register_device来对vhost 设备呈现并产生关联。vDPA的主要结构框图可参见图4所示。

d70f24bdb0e401cda9817ad3584f861f.png

图4. vDPA用户态驱动实现时的模块关系

(IO路径如红色线标识)

DPDK代码中现有支持的vdpa的设备驱动目前主要有:ifc、mlx5和sfc这三种。这里以ifc驱动的为例,将其主要机制概述如下:

1. 匹配设备并调用vdpa设备驱动进行初始化;

“DPDK/drivers/vdpa/ifc” 目录下为Intel的SmartNIC的对应vdpa驱动,其在DPDK框架中以普通的pci设备驱动注册,如RTE_PMD_REGISTER_PCI(net_ifcvf, rte_ifcvf_vdpa)所示。驱动定义了支持的设备vendor ID和device ID,当启动时通过“-a”参数传给DPDK进程允许使用的设备被匹配到时,就会调用ifcvf_pci_probe设备进行初始化。在ifcvf_pci_probe函数中,即会调用rte_vdpa_register_device来注册vdpa设备和函数接口;

2. vhost机制获取注册的vdpa设备并在frond-end建链时关联;

这个过程主要可以分成三个阶段:

1) DPDK的vdpa应用在调用前边提到的rte_vhost_driver_start启动vhost server之前通过函数 rte_vhost_driver_attach_vdpa_device,将vdpa驱动注册的vdpa设备添加到标识server的vhost_user_socket 句柄中描述数据结构中;

2) 当vhost server响应front-end驱动发起的建链请求时,即会在vhost_user_add_connection中执行vhost_attach_vdpa_device将vdpa设备复制给描述与这个front-end驱动关系链接的virtio_net数据结构中;

3) 当vhost_user_msg_handler处理front-end发送过来的控制消息时,会获取vdpa的设备,且第一执行时会通过“vdpa_dev->ops->dev_conf(dev->vid, vdpa_qid)” 调用来通知硬件设备配置好virtqueue。

3. vdpa硬件设备处理front-end驱动的IO请求并进行回应;

vdpa模式下,virtqueue的数据处理由硬件完成,因而在注册的 “notify_ops->new_device” 函数接口中,不需要执行virtqueue相关的处理操作。

DPDK代码中现可跑起来的vdpa的example在 “DPDK/examples/vdpa” 目录下。可以通过编译DPDK时由 “-Dexamples=vdpa” 来指定编译,并参考http://doc.dpdk.org/guides/sample_app_ug/vdpa.html 中的说明进行使用。

用户态virtio虚拟化

实现验证实践

在本章节将对前边介绍的virtio实现进行验证举例。涉及的具体命令以引号和斜体标识。

virtio用户态实现验证及实践

前边介绍了virtio用户态实现的情况,接下来本文将继续描述针对部分用户态实现场景的验证情况。主要介绍两个方面的案例:其一,后端以vhost-blk提供Virtio block设备给KVM-Qemu VM使用;其二,通过virtio-blk-user的方式访问后端vhost-blk。

 后端使用vhost-blk提供Virtio block设备给KVM-Qemu VM使用

该项测试就是在Host系统下启动基于SPDK的vhost-blk程序,以Malloc类型的spdk_bdev作为实际的IO处理单元来向Qemu进程启动的VM提供virtio的设备。对应的步骤可以分为如下几个部分来介绍。

创建vhost-blk设备

创建vhost-blk设备的操作在Host系统下执行。

当前vhost-blk的功能主要通过rpc命令的方式来支持。在spdk编译前的configure命令执行时加上“--with-vhost”参数。在编译后的文件目录“build/bin”目录下会有vhost的可执行程序,运行vhost后可以通过rpc命令创建vhost-blk的back-end设备。相关步骤命令及说明罗列如下。

1. 开启大页配置:“./scripts/setup.sh”

2. 运行vhost程序:“./build/bin/vhost -S /var/tmp -m 0x3”。//其中,-S参数指定用户态vhost back-end设备对应创建socket的路径,-m指定使用的cpu核。

3. 创建Malloc类型的spdk_bdev设备:“./scripts/rpc.py bdev_malloc_create 64 512 -b Malloc0”

4. 创建vhost-blk设备并添加spdk_bdev设备:“./scripts/rpc.py vhost_create_blk_controller --cpumask 0x1 vhost.1 Malloc0”。//其中cpumask参数指定了vhost-blk 设备能够使用的CPU核。

该命令执行后可以在指定的目录下看到对应的用于访问vhost设备的文件(标识对应的vhost server unix socket的句柄)。如此处应为/var/tmp/vhost.1。

启动虚拟机将vhost-blk设备分配给虚拟机

将上述创建的vhost-blk设备对应socket的访问文件传递给Qemu,即可以实现将vhost-blk设备分配给虚拟机。

启动虚拟机命令:“qemu-system-x86_64 -cpu host -smp 8 -m 4G -object memory-backend-file,id=mem,size=4G,mem-path=/dev/hugepages,share=on -numa node,memdev=mem -drive file=/home/zj/qemu_test/qemu-img/fedora/Fedora35_qcow2.img,if=none,id=disk -device ide-hd,drive=disk,bootindex=0 -net user,hostfwd=tcp::10020-:22 -net nic -chardev socket,id=char0,path=/var/tmp/vhost.1 -device vhost-user-blk-pci,chardev=char0,id=blk0 -vnc :1 --enable-kvm”

命令中“-object”开始到“share=on”的部分是设置Guest的内存能够被vhost-blk back-end设备访问,“-chardev”开始的部分是指定Host下创建的vhost-blk设备给虚拟机使用,且Qemu通过path指定的路径来访问。

虚拟机下使用virtio-blk用户态front-end驱动访问block设备

虚拟机启动后可以看到系统下有Virtio block device的pci controller。 

f918ce10c7f42a62d83f960096b0ccd8.png

图5. 在虚拟机下看到的virtio block device pci controller

在虚拟机里下载SPDK的代码,执行configure命令时加上“--with-virtio” 和 “--with-fio=”,其中fio-path是指fio源码的路径,主要是为了使用fio的头文件和库(因而需要先下载fio代码)。若使用rpc命令方式来通过bdevperf对用户态virtio front-end驱动创建出来的spdk_bdev跑测试的具体步骤可以归集如下(以下各个命令均在虚拟机下执行)。

1.设置大页及将pci设备交由uio驱动接管: ./scripts/setup.sh。

需要注意两个点:其一,VM中默认未添加IOMMU的模拟设备,即无vIOMMU,因而默认在VM下会使用uio驱动来将pci设备资源映射到用户态(如需要添加vIOMMU,可以在Qemu命令行中加上-device intel-iommu,intremap=on参数);其二,setup.sh脚本中会根据SPDK/include/spdk/pci_ids.h中定义的VIRTIO设备的device/vendor ID信息来rebind设备。

dd4553e6d6b4120a63136af8f12cc0d4.png

图6. 使用SPDK setup.sh脚本rebind virtio block device pci controller

2. 启动SPDK应用程序bdevperf:./test/bdev/bdevperf -q 128 -o 4096 -w randread -t 160 -S 5 -z;   //其中-S参数指定多久显示一次测试结果,-z参数标识启动后先不进行测试,由rpc命令来启动测试。

3.指定virtio block device的pci addr创建virtio-blk类型的spdk_bdev:“./scripts/rpc.py bdev_virtio_attach_controller -t pci -a "00:04.0" -d blk virtioblk0”。  //其中,-t pci指定virtio front-end驱动访问的是pci的设备, -d指定是blk类型的设备,创建的bdev的名称为virtioblk0。

fb79924d9f5f9363dfa8df20b1202ae6.png

图7. 虚拟机中通过rpc命令创建virtio blk bdev

4. 通过bdevperf脚本启动测试:“PYTHONPATH=$PYTHONPATH:./scripts/ test/bdev/bdevperf/bdevperf.py -t 160 perform_tests”。在执行该命令时需要注意SPDK代码中各个模块的rpc脚本的存放目录rpc在哪个地方,对于rpc的路径为“./scripts/rpc/”时(此时bdevperf.py中import的是rpc)可以直接执行前边给出的命令;对于rpc的路径为“./python/spdk/rpc/”时(此时bdevperf.py中import的是spdk.rpc),命令需要变为“PYTHONPATH=$PYTHONPATH:./python/ test/bdev/bdevperf/bdevperf.py -s /var/tmp/spdk1.sock -t 200 perform_tests”

9cc49559a75c893c216f94694decbada.png

图8. 对virtio bdev跑bdevperf的测试结果显示

通过virtio-blk-user访问vhost-blk的后端

这项测试就是在Host系统下先创建vhost-blk的用户态back-end的设备,然后通过在Host系统下启动另一个SPDK进程模拟bare-metal应用的进程来访问back-end的设备。所有的操作命令均在Host系统下执行。

创建vhost-blk设备

创建vhost-blk设备的步骤与前边的case中的操作一样,但注意编译SPDK时加上”--with-virtio”和“--with-fio=”选项,其中fio-path是指fio源码的路径,主要是为了使用fio的头文件和库(因而需要先下载fio代码)。

且在启动vhost应用程序时加上-g参数,指定SPDK程序创建一个hugetlbfs file。

在host下启动另外的SPDK进程充当front-end驱动跑测试

启动另外的SPDK进程模拟bare-metal应用。仍然以bdevperf为例来进行介绍。

1.启动bdevperf进程:“./test/bdev/bdevperf/bdevperf -r /var/tmp/spdk1.sock -q 128 -o 4096 -w randread -t 200 -S 5 -z -g”。   //-r参数给新的SPDK进程指定新的rpc调用访问的socket句柄对应的文件,以和vhost-blk设备创建的SPDK进程区分开来,-g参数设置与vhost进程一致,-S参数指定多久显示一次测试结果,-z参数标识启动后先不进行测试,由rpc命令来启动测试。

2. 创建virtio-blk类型的spdk_bdev设备:“./scripts/rpc.py -s /var/tmp/spdk1.sock  bdev_virtio_attach_controller -t user -a /var/tmp/vhostblk0 -d blk virtioblk0”。//-s参数是指定rpc命令server对应的socket句柄文件,-t user指定virtio back-end为user模式,通过-a参数指定的文对应的unix socket访问,-d指定bdev为blk类型,名称为virtioblk0 。

3. 通过bdevperf.py脚本启动针对创建的virtio blk spdk_bdev的测试:“PYTHONPATH=$PYTHONPATH:./scripts/ test/bdev/bdevperf/bdevperf.py -s /var/tmp/spdk1.sock -t 200 perform_tests”。在执行该命令时需要注意SPDK代码中各个模块的rpc脚本的存放目录rpc在哪个地方,对于rpc的路径为“./scripts/rpc/”时(此时bdevperf.py中import的是rpc)可以直接执行前边给出的命令;对于rpc的路径为“./python/spdk/rpc/”时(此时bdevperf.py中import的是spdk.rpc),命令需要变为“PYTHONPATH=$PYTHONPATH:./python/ test/bdev/bdevperf/bdevperf.py -s /var/tmp/spdk1.sock -t 200 perform_tests”

2292d993a461f54710a9ef9e31ddff84.png

图9. 使用bdevperf对virtio-user模式下的virtio bdev设备跑测试的结果显示

ce1f7e9a42785b6bef2644b0d78c7723.gif

注: 有关vfio-user用户态实现分析及验证的内容将会在【下篇】发出,敬请期待!

参考信息

1. https://www.redhat.com/en/blog/introduction-virtio-networking-and-vhost-net 

2. https://www.youtube.com/watch?v=paTvtJ6JdAc 

3. https://www.redhat.com/en/blog/journey-vhost-users-realm 

4. https://www.redhat.com/en/blog/how-deep-does-vdpa-rabbit-hole-go 

5. https://spdk.io/doc/vhost.html

6. http://doc.dpdk.org/guides/sample_app_ug/vdpa.html

7. https://github.com/tmakatos/qemu/blob/master/docs/devel/vfio-user.rst 

8. https://github.com/nutanix/libvfio-user/blob/master/docs/spdk.md

b0f39c70ea8ae0858c5accdfcf22fcfe.png

转载须知

DPDK与SPDK开源社区

公众号文章转载声明

推荐阅读

从SPDK Blobstore到 Blob FS

Intel Scalable IOV介绍及应用实例

SPDK Delay Bdev 介绍及应用实例

支持非对称命名空间访问的SPDK多路径验证

90ae037ed46acb56c4c633eacc03bcd5.png

348056f419946988e05b217ce6f795ed.gif

点点“赞”“在看”,给我充点儿电吧~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

用户态虚拟化IO通道实现概览及实践(上) 的相关文章

  • 在Python中获取文件描述符的位置

    比如说 我有一个原始数字文件描述符 我需要根据它获取文件中的当前位置 import os psutil some code that works with file lp lib open path to file p psutil Pro
  • 在两个活动之间传输数据[重复]

    这个问题在这里已经有答案了 我正在尝试在两个不同的活动之间发送和接收数据 我在这个网站上看到了一些其他问题 但没有任何问题涉及保留头等舱的状态 例如 如果我想从 A 类发送一个整数 X 到 B 类 然后对整数 X 进行一些操作 然后将其发送
  • IO 密集型任务中的 Python 多线程

    建议仅在 IO 密集型任务中使用 Python 多线程 因为 Python 有一个全局解释器锁 GIL 只允许一个线程持有 Python 解释器的控制权 然而 多线程对于 IO 密集型操作有意义吗 https stackoverflow c
  • 使用Caliper时如何指定命令行?

    我发现 Google 的微型基准测试项目 Caliper 非常有趣 但文档仍然 除了一些示例 完全不存在 我有两种不同的情况 需要影响 JVM Caliper 启动的命令行 我需要设置一些固定 最好在几个固定值之间交替 D 参数 我需要指定
  • 总是使用 Final?

    我读过 将某些东西做成最终的 然后在循环中使用它会带来更好的性能 但这对一切都有好处吗 我有很多地方没有循环 但我将 Final 添加到局部变量中 它会使速度变慢还是仍然很好 还有一些地方我有一个全局变量final 例如android Pa
  • 如何在 javadoc 中使用“<”和“>”而不进行格式化?

    如果我写
  • Jupyter Notebook 内核一直很忙

    我已经安装了 anaconda 并且 python 在 Spyder IPython 等中工作正常 但是我无法运行 python 笔记本 内核被创建 它也连接 但它始终显示黑圈忙碌符号 防火墙或防病毒软件没有问题 我尝试过禁用两者 我也无法
  • Fabric env.roledefs 未按预期运行

    On the 面料网站 http docs fabfile org en 1 10 usage execution html 给出这个例子 from fabric api import env env roledefs web hosts
  • 向 Altair 图表添加背景实心填充

    I like Altair a lot for making graphs in Python As a tribute I wanted to regenerate the Economist graph s in Mistakes we
  • 每个 X 具有多个 Y 值的 Python 散点图

    我正在尝试使用 Python 创建一个散点图 其中包含两个 X 类别 cat1 cat2 每个类别都有多个 Y 值 如果每个 X 值的 Y 值的数量相同 我可以使用以下代码使其工作 import numpy as np import mat
  • 对年龄列进行分组/分类

    我有一个数据框说df有一个柱子 Ages gt gt gt df Age 0 22 1 38 2 26 3 35 4 35 5 1 6 54 我想对这个年龄段进行分组并创建一个像这样的新专栏 If age gt 0 age lt 2 the
  • 有没有办法检测正在运行的代码是否正在上下文管理器内执行?

    正如标题所述 有没有办法做到这样的事情 def call back if called inside context print running in context else print called outside context 这将
  • Google App Engine 如何预编译 Java?

    App Engine 对应用程序的 Java 字节码使用 预编译 过程 以增强应用程序在 Java 运行时环境中的性能 预编译代码的功能与原始字节码相同 有没有详细的信息这是做什么的 我在一个中找到了这个谷歌群组消息 http groups
  • 无法捆绑适用于 Mac 的 Java 应用程序 1.8

    我正在尝试将我的 Java 应用程序导出到 Mac 该应用程序基于编译器合规级别 1 7 我尝试了不同的方法来捆绑应用程序 1 日食 我可以用来在 Eclipse 上导出的最新 JVM 版本是 1 6 2 马文 看来Maven上也存在同样的
  • 有人用过 Dabo 做过中型项目吗? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我们正处于一个新的 ERP 风格的客户端 服务器应用程序的开始阶段 该应用程序是作为 Python 富客户端开发的 我们目前正在评估 Dabo
  • 在mockito中使用when进行模拟ContextLoader.getCurrentWebApplicationContext()调用。我该怎么做?

    我试图在使用 mockito 时模拟 ContextLoader getCurrentWebApplicationContext 调用 但它无法模拟 here is my source code Mock org springframewo
  • 如何计算 pandas 数据帧上的连续有序值

    我试图从给定的数据帧中获取连续 0 值的最大计数 其中包含来自 pandas 数据帧的 id date value 列 如下所示 id date value 354 2019 03 01 0 354 2019 03 02 0 354 201
  • 获取 JVM 上所有引导类的列表?

    有一种方法叫做findBootstrapClass对于一个类加载器 如果它是引导的 则返回一个类 有没有办法找到类已经加载了 您可以尝试首先通过例如获取引导类加载器呼叫 ClassLoader bootstrapLoader ClassLo
  • 使用 Python 的 matplotlib 选择在屏幕上显示哪些图形以及将哪些图形保存到文件中

    我想用Python创建不同的图形matplotlib pyplot 然后 我想将其中一些保存到文件中 而另一些则应使用show 命令 然而 show 显示all创建的数字 我可以通过调用来避免这种情况close 创建我不想在屏幕上显示的绘图
  • 从列表指向字典变量

    假设你有一个清单 a 3 4 1 我想用这些信息来指向字典 b 3 4 1 现在 我需要的是一个常规 看到该值后 在 b 的位置内读写一个值 我不喜欢复制变量 我想直接改变变量b的内容 假设b是一个嵌套字典 你可以这样做 reduce di

随机推荐

  • MySQL导入.sql文件方法以及导入失败的问题解决

    首先这是聂老师的sql文件 MySQL dump 10 13 Distrib 8 0 27 for Win64 x86 64 Host localhost Database teaching Server version 8 0 27 40
  • Linux创始者托瓦兹谈及IoT --「安全在其次」

    本文译至 http japan zdnet com article 35080722 2 圣迭戈 Linux之父Linus Torvalds 在Linux Foundation主办的活动 Embedded Linux Conference
  • 阿里云学生计划领取攻略

    阿里云学生计划题目答案分享 由于朋友需要 便整理了一下给出分享 笔者概念性东西不是很好 如有错误多多包涵 文章部分题目来自网友 先给出阿里云学生疫情领取地址 点击这里这里这里这里 参考答案如下 数据库管理系统是 B A 操作系统的一部分 B
  • NETCore入门系列(Log4NET组件的使用)

    文章目录 分析 整合Log4net 源码 分析 1 官方自带的Log中间件可在命令行中输出日志 通过在构当前控制器的构造函数中注入 如下图 2 此时如果想要将日志输出到项目的某个文件中 则可以通过整合Log4net组件 3 一般建议日志记录
  • 算法:z字形排列

    将一个给定字符串根据给定的行数 以从上往下 从左到右进行 Z 字形排列 class Solution public string convert string s int numRows string result 如果排序长度为1 或者字
  • Python,OpenCV中的非局部均值去噪(Non-Local Means Denoising)

    Python OpenCV中的非局部均值去噪 Non Local Means Denoising 1 效果图 2 原理 3 源码 2 1 单彩色图去噪 2 2 多连续彩色帧去噪 参考 这篇博客将介绍不同的计算摄影技术 非局部均值去噪 Non
  • MYSQL 命令大全

    一 连接MySQL 格式 mysql h 主机地址 u 用户名 p 用户密码 1 例1 连接到本机上的MYSQL 首先在打开DOS 窗口 然后进入目录 mysqlbin 再键入命令mysql uroot p 回车后提示你输密码 如果刚安装好
  • PHY调试经验

    1 PHY调试过程 1 设备树中配置正确的PHY ADDR PHY ID clause 45或者22协议 PHY ADDR配置不正确会导致MDC MDIO通信不正常或失败 PHY ID用于匹配PHY驱动程序 2 通过MDC MDIO读写PH
  • Google亲儿子 Nexus/Pixel 手机刷机Root之旅

    简介 本文介绍的方法是针对Google亲儿子的教程 其他国内厂商请绕道 1 解锁 1 1 OEM解锁 想要做下面这些事 需要先在开发者选项里打开oem解锁 如果你的手机是V版 运营商定制版 请看这里 oem解锁选项灰色 1 2 进入boot
  • 【JDBC】关于JDBC入门和一些见解

    关于JDBC的一些理解和总结 JDBC连接数据库 刚开始学的时候经常忘记步骤 其实多了几次之后发现完全就是自己没有理解到原理 现在回头看还是挺有意思的 分为以下几个步骤 1 注册加载JDBC驱动 把Driver装进JVM Class for
  • Centos 7 重启网卡报错解决方案

    一 Network 当重启网卡时报错 解决方案 步骤1 修改对应文件 增加命令 步骤2 关闭NetworkManager服务 并重启网卡 systemctl stop NetworkManager systemctl restart net
  • 解谜元宇宙元年的十个疑问

    解谜元宇宙元年的十个疑问 2021年 元宇宙突然出现在大家的视野之中 相关概念受到资本的热捧 成为金融市场的热点 这难免会让我们对元宇宙产生很多好奇和疑问 本文总结了十个对元宇宙的疑问 并一一作出解答 2021年为什么是元宇宙元年 元宇宙
  • 关于运算放大器电流流向的问题

    前言 一 问题的引入 二 提出问题 三 问题解答 写在结尾的话 前言 问题缘起于一次硬件同事之间的讨论 虽然目前我不是做硬件的 但签于我的专业以及之前从事的工作 觉得有必要把记录下来 后期也打算写一些站在学习者的角度 关于硬件知识的学习心得
  • 数字电路和模拟电路-8触发器

    前言 掌握锁存器原理及应用 基本SR锁存器 钟控SR锁存器 钟控D锁存器 钟控D锁存器的动态参数 掌握触发器原理及应用 主从触发器 维持阻塞触发器 其它功能的触发器 目录 一 基本SR锁存器 1 双稳态电路 Bistate Elements
  • Android系统Unity使用HttpWebRequest访问Https请求出现连接超时

    多渠道版本配置网络地址时 http地址替换为了https 由于粗心大意 之前同事遗留的请求框架代码没有对https协议进行 处理 导致在android手机下unity访问https地址进行配置文件下载更新时出现连接超时问题 解决方案 if
  • word vba设置表格样式

    Sub 表格处理 功能 光标在表格中处理当前表格 否则处理所有表格 Application ScreenUpdating False 关闭屏幕刷新 Application DisplayAlerts False 关闭提示 On Error
  • java连接db2数据库示例代码_java实现连接db2数据库的代码实例

    java实现连接db2数据库的代码实例 第一种 目前ibm一直都没有提供type 1的jdbc驱动程序 第二种 类型2驱动 com ibm db2 jdbc app db2driver 该驱动也位于包db2java zip中 jdk必须能访
  • uniapp 微信小程序长按识别二维码,跳转小程序、个人微信

    前言 业务要求是小程序放一个二维码图片 长按可以识别二维码 进而识别出个人微信 添加个人微信 我们可以通过uni previewImage OBJECT 或者 wx previewImage Object object 预览当前图片去实现
  • 24-系统自带的 Win+R 功能

    Win 运行窗口 Win R 开始菜单 gt 运行 是 Windows 的一个原生的功能 从 XP 到 Windows 10 都自带了 当用户按下快捷键 Win R Win 为键盘上Windows图标键 后 系统会弹出一个小窗口让你输入命令
  • 用户态虚拟化IO通道实现概览及实践(上)

    自虚拟化技术诞生起 提升虚拟化场景中IO设备性能和驱动的兼容性 可扩展性一直是备受关注和追求的目标 随着半虚拟化技术的出现 virtio设备及驱动也很快流行并逐步变成了虚拟化应用中的主要IO通道形态 例如 virtio现已支持实现的设备涵盖