0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

virtio I/O通信流程及设备框架的实现

openEuler 来源:openEuler 作者:openEuler 2022-03-10 13:37 次阅读

virtio 是一种通用的半虚拟化的 I/O 通信协议,提供了一套前后端 I/O 通信的的框架协议和编程接口。根据该协议实现的设备通过前后端的配合,相比全模拟设备可以大幅减少陷入陷出以及内存拷贝的次数,使 guest 获得高效的 I/O 性能。作为目前虚拟化标准的通用协议规范,经历了 0.95、1.0、1.1 三个版本的演进。根据 0.95 版本实现的称为传统 virtio 设备,1.0 版本修改了一些 PCI 配置空间的访问方式和 virtioqueue 的优化和特定设备的约定,1.1 版本则增加了 packed virtqueue 的支持,详细可以参考官方发布的 virtio 协议规范。

之所以称 virtio 是一种半虚拟化的解决方案,是因为其首先需要在主机侧通过软件创建 virito 的后端设备,其次在 Guest 要有对应的设备前端驱动,前后端通过共享内存进行通信。virtio 规范定义了设备的控制和数据面接口,控制面接口包括设备状态、feature 的协商等,数据面则包括共享内存的数据布局定义以及前后端的通知方式。基于 virtio 协议,目前已衍生出了 virtio-blk、virtio-net、virtio-scsi、virtio-mem 等各种各样的半虚拟化设备。virtio 设备可以支持多种传输层协议,既可以挂载到 MMIO 总线,也可以作为 PCI 设备,另外还可以支持基于标准 I/O 通道机制的 S/390 设备。

4963ac88-a026-11ec-952b-dac502259ad0.png

鉴于 virtio 设备具备较好的性能及通用性,StratoVirt 自然也支持,StratoVirt 中 virtio 设备的实现架构以及 I/O 通信流程如上图所示。下面就基于目前最新的代码,探究一下 StratoVirt 中 virtio 设备的代码实现框架。

VirtioDevice Trait

StratoVirt 的 virtio crate 提供了 virtio 设备的通用接口以及所有 virtio 设备的相关实现。其中,lib.rs 中定义了为所有 virtio 设备定义的 VirtioDevice Trait。每种 virtio 设备都需要实现自定义的 VirtioDevice 接口。

///Thetraitforvirtiodeviceoperations.
pubtraitVirtioDevice:Send{
///Realizelowleveldevice.
fnrealize(&mutself)->Result<()>;

///Unrealizelowleveldevice
fnunrealize(&mutself)->Result<()>{
bail!("Unrealizeofthevirtiodeviceisnotimplemented");
}

///Getthevirtiodevicetype,refertoVirtioSpec.
fndevice_type(&self)->u32;

///Getthecountofvirtiodevicequeues.
fnqueue_num(&self)->usize;

///Getthequeuesizeofvirtiodevice.
fnqueue_size(&self)->u16;

///Getdevicefeaturesfromhost.
fnget_device_features(&self,features_select:u32)->u32;

///Setdriverfeaturesbyguest.
fnset_driver_features(&mutself,page:u32,value:u32);

///Readdataofconfigfromguest.
fnread_config(&self,offset:u64,data:&mut[u8])->Result<()>;

///Writedatatoconfigfromguest.
fnwrite_config(&mutself,offset:u64,data:&[u8])->Result<()>;

///Activatethevirtiodevice,thisfunctioniscalledbyvcputhreadwhenfrontend
///virtiodriverisreadyandwrite`DRIVER_OK`tobackend.
///
///#Arguments
///
///*`mem_space`-Systemmem.
///*`interrupt_evt`-Theeventfdusedtosendinterrupttoguest.
///*`interrupt_status`-Theinterruptstatuspresenttoguest.
///*`queues`-Thevirtioqueues.
///*`queue_evts`-Thenotifiereventsfromguest.
fnactivate(
&mutself,
mem_space:Arc,
interrupt_cb:Arc,
queues:&[Arc>],
queue_evts:Vec,
)->Result<()>;

///Deactivatevirtiodevice,thisfunctionremoveeventfd
///ofdeviceoutoftheeventloop.
fndeactivate(&mutself)->Result<()>{
bail!(
"Resetthisdeviceisnotsupported,virtiodevtypeis{}",
self.device_type()
);
}

///Resetvirtiodevice.
fnreset(&mutself)->Result<()>{
Ok(())
}

///UpdatethelowlevelconfigofMMIOdevice,
///forexample:updatetheimagesfilefdofvirtioblockdevice.
///
///#Arguments
///
///*`_file_path`-Therelatedbackendfilepath.
fnupdate_config(&mutself,_dev_config:OptiondynConfigCheck>>)->Result<()>{
bail!("Unsupportedtoupdateconfiguration")
}
}
  • realize()/unrealize(): 这一组接口用于具现化/去具现化具体的 virtio 设备。具现化做的一些具体操作包括设置支持的 features、设备特有的属性(如网卡的 mac)、初始化连接 Host 后端设备等。
  • set_driver_features():将前端驱动支持的 features 与后端模拟设备支持的 features 进行协商后,设置最终实现的 features。
  • read_config()/write_config():virtio 协议规范为每种 virtio 设备定义了自定义的配置空间,这组接口就是用来读写这部分配置数据。
  • activate()/deactivate(): 激活/去激活设备,负责绑定/解绑后端、加入/移除 I/O 循环。
  • reset():虚拟机重启时某些设备需要重置。
  • update_config():支持轻量机型下的 virtio-mmio 设备动态绑定/解绑后端,实现 virtio-mmio 设备的模拟热插拔。

virtqueue

499e7368-a026-11ec-952b-dac502259ad0.png

virtio 设备可以有一个或多个队列,每个队列有描述符表、available ring、used ring 三个部分。当前 StratoVirt 的 virtio 设备均遵循 1.0 规范,队列的内存布局仅支持 Split Vring 的方式。queue.rs 中定义了一系列针对队列操作及查询的接口。所有的 I/O 请求数据信息以描述符的形式存放在描述符表中,前端准备好数据后更新 available ring 告诉后端还有哪些 I/O 待发送,后端执行完 I/O 更新 used ring 通知前端。不同设备的 I/O 处理不尽相同,但是核心的 virtqueue 操作是一样的。

pubstructSplitVring{
///Regioncacheinformation.
pubcache:Option,
///Guestphysicaladdressofthedescriptortable.
///Thetableiscomposedofdescriptors(SplitVringDesc).
pubdesc_table:GuestAddress,

///Guestphysicaladdressoftheavailablering.
///Theringiscomposedofflags(u16),idx(u16),ring[size](u16)andused_event(u16).
pubavail_ring:GuestAddress,

///Guestphysicaladdressoftheusedring.
///Theringiscomposedofflags(u16),idx(u16),used_ring[size](UsedElem)andavail_event(u16).
pubused_ring:GuestAddress,

///Hostaddresscache.
pubaddr_cache:VirtioAddrCache,

///Indicatewhetherthequeueconfigurationisfinished.
pubready:bool,

///Themaximalsizeinelementsofferedbythedevice.
pubmax_size:u16,

///Thequeuesizesetbyfrontend.
pubsize:u16,

///Interruptvectorindexofthequeueformsix
pubvector:u16,

///Thenextindexwhichcanbepoppedintheavailablevring.
next_avail:Wrapping<u16>,

///Thenextindexwhichcanbepushedintheusedvring.
next_used:Wrapping<u16>,

///Theindexoflastdescriptorusedwhichhastriggeredinterrupt.
last_signal_used:Wrapping<u16>,
}

virtio-mmio 设备

StratoVirt 目前提供两种机型:轻量机型和标准机型。轻量机型由于需要追求极致的启动速度以及内存底噪开销,因此只支持挂载数量有限的 virtio-mmio 设备。而标准机型面向传统的标准云化场景,对于 I/O 设备的性能要求较高,且需要支持热插拔满足资源弹性,因此标准机型支持将 virtio 设备以 PCI 设备挂载在模拟的 PCI 总线上。目前标准机型只支持配置 virtio-pci 设备,不支持 virtio-mmio 设备。

结构体 VirtioMmioDevice 定义了一个通用的 virtio-mmio 设备,其中的 device 即为实现了 VirtioDevice 这个 trait 的具体的 virtio 设备结构,可以是网卡、磁盘等。VirtioMmioState 结构体中存放了 virtio-mmio 设备的控制寄存器,并且为其实现了对应的读写接口 read_common_config()/write_common_config()。virtio-mmio 设备的配置空间布局如下图所示:

49b533a0-a026-11ec-952b-dac502259ad0.png

interrupt_evt 通过 irqfd 向虚拟机注入中断,host_notify_info 则为每个队列创建了一个 eventfd,虚拟机利用 ioeventfd 机制陷出到 StratoVirt 执行后端的 I/O 处理。

pubstructVirtioMmioDevice{
//Theentityoflowleveldevice.
pubdevice:ArcdynVirtioDevice>>,
//EventFdusedtosendinterrupttoVM
interrupt_evt:EventFd,
//Interruptstatus.
interrupt_status:Arc,
//HostNotifyInfousedforguestnotifier
host_notify_info:HostNotifyInfo,
//Thestateofvirtiommiodevice.
state:VirtioMmioState,
//Systemaddressspace.
mem_space:Arc,
//Virtioqueues.
queues:Vec>>,
//SystemResourceofdevice.
res:SysRes,
}

VirtioMmioDevice 实现了 realize 接口完成设备的具现化:

  1. 调用各设备实现的 VirtioDevice trait 的具现化接口。
  2. virtio-mmio 设备挂载在了系统总线上,StratoVirt 为每个设备分配 512 字节的配置空间。除此之外,需要为其注册 irqfd 以便后续 I/O 完成后向虚拟机注入中断。这些信息都保存在 SysRes 数据结构中。
  3. 添加内核启动参数,通过内核启动参数将设备的内存区间及中断号信息直接告诉 Guest。
pubfnrealize(
mutself,
sysbus:&mutSysBus,
region_base:u64,
region_size:u64,
#[cfg(target_arch="x86_64")]bs:&Arc>,
)->ResultSelf>>>{
self.device
.lock()
.unwrap()
.realize()
.chain_err(||"Failedtorealizevirtio.")?;

ifregion_base>=sysbus.mmio_region.1{
bail!("Mmioregionspaceexhausted.");
}
self.set_sys_resource(sysbus,region_base,region_size)?;
letdev=Arc::new(self));
sysbus.attach_device(&dev,region_base,region_size)?;

#[cfg(target_arch="x86_64")]
bs.lock().unwrap().kernel_cmdline.push(Param{
param_type:"virtio_mmio.device".to_string(),
value:format!(
"{}@0x{:08x}:{}",
region_size,
region_base,
dev.lock().unwrap().res.irq
),
});
Ok(dev)
}

前端驱动加载过程中会读写设备的配置空间,前后端完成 feature 的协商,一切 OK 后前端驱动将向配置空间写状态,后端设备将会调用 activate 方法激活设备。当触发激活时,前端已为这三个部分分配了内存空间,Guest 物理地址(GPA)已写入设备的配置空间,后端需要将 GPA 地址转化为 Host 虚拟地址(HVA)。随后,就可以根据队列配置创建队列,并将 I/O 的 eventfd 加入事件循环激活设备开始 I/O 通信。

virtio-pci 设备

如上所述,virtio 设备也可以作为一个 PCI 类设备挂载到 PCI 总线上。类似的,在 StratoVirt 中用结构体 VirtioPciDevice 来表示一个 virtio-pci 设备。既然是作为一个 PCI 设备,virtio-pci 就需要拥有符合 PCI 规范拥有 PCI 设备的配置空间,Guest 启动后通过 PCI 设备树枚举来发现设备,而不是像 virtio-mmio 设备一样直接通过内核启动参数告诉 Guest。

pubstructVirtioPciDevice{
///Nameofthisdevice
name:String,
///Theentityofvirtiodevice
device:ArcdynVirtioDevice>>,
///Deviceid
dev_id:Arc,
///Devfn
devfn:u8,
///Ifthisdeviceisactivatedornot.
device_activated:Arc,
///MemoryAddressSpace
sys_mem:Arc,
///Pciconfigspace.
config:PciConfig,
///VirtiocommonconfigrefertoVirtioSpec.
common_config:Arc>,
///PrimaryBus
parent_bus:Weak>,
///Eventfdsusedfornotifyingtheguest.
notify_eventfds:NotifyEventFds,
///Thefunctionforinterrupttriggering
interrupt_cb:Option>,
///Virtioqueues.ThevectorandQueuewillbesharedacrossingthread,soallwithArc>wrapper.
queues:ArcVec>>>>,
///Multi-Functionflag.
multi_func:bool,
}

VirtioPciDevice 通过实现 PciDevOps trait 的 realize()方法完成设备的具现化:

  1. 初始化 PCI 配置寄存器
  2. 将 virtio 协议规定的 common configuration、notifications、ISR status、Device-specific configuration 作为四个 PCI 设备的 capability, 对应数据的内存空间则映射到第 3 个 BAR 空间的不同部分。配置空间布局如下图所示:

49ca4dda-a026-11ec-952b-dac502259ad0.png

  1. 前端驱动对于各空间的访问的回调函数由 modern_mem_region_init()注册,当前端读写这部分内存区间时会陷出到 StratoVirt 执行注册的回调接口。每个队列在 notification cap 指向的空间中占据 4 个字节,StratoVirt 为每个队列的 4 个字节空间注册 ioeventfd。前端驱动准备好某个队列后,就会写对应队列的这 4 个字节的地址空间,后端借助 ioeventfd 机制收到通知后陷出进行 host 侧的 I/O 下发。
  2. 中断机制采用 MSI-X,向量表和 pending 位图则位于第 2 个 BAR 空间。

审核编辑:郭婷
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • PCI
    PCI
    +关注

    关注

    4

    文章

    660

    浏览量

    130116
  • 虚拟化
    +关注

    关注

    1

    文章

    361

    浏览量

    29752

原文标题:StratoVirt 的 virtio 设备模拟是如何实现的

文章出处:【微信号:openEulercommunity,微信公众号:openEuler】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    linux设备virtio组织关系及设备初始化调用流程

    从哪里开始初始化的?要理清这些关系需要以linux设备驱动模型为背景展开讨论。这篇文章,我们以linux kernel 3.10代码为例,分析一下virtio的相关组织关系,以及设备
    的头像 发表于 09-25 15:47 4621次阅读
    linux<b class='flag-5'>设备</b>中<b class='flag-5'>virtio</b>组织关系及<b class='flag-5'>设备</b>初始化调用<b class='flag-5'>流程</b>

    请问怎样设计并实现闪存设备I/O软件?

    闪存设备管理技术的现状及存在的问题是什么?闪存设备I/O软件的分层结构是怎样的?怎样设计并实现闪存设备
    发表于 04-27 06:44

    RT-Thread中I2C设备的驱动框架对接如何实现

    GPIO模拟的方式实现I2C通讯。RT-Thread 提供了一套 I/O 设备管理框架,它把
    发表于 05-19 17:11

    StratoVirt 的 virtio-blk 设备是如何实现的?

    平台下虚拟磁盘的一种实现方式,本质上为一种半模拟技术。virtio-blk 设备中采用 io_event_fd 进行前端到后端通知,采用中断注入方式实现后端到前端的通知,并通过 IO
    发表于 06-29 10:37

    StratoVirt 中的虚拟网卡是如何实现的?

    基于 virtio 协议的半虚拟化框架实现前后端通信Virtio 协议是一种在半虚拟化场景中使用的
    发表于 08-10 11:16

    RT-Tread设备驱动开发之I/O设备框架简析

    获得正确的设备驱动,然后通过这个设备驱动与底层I/O硬件设备进行数据(或控制)交互。I/
    发表于 03-15 14:44

    输入输出设备I/O设备总结

    I/O设备又叫输入输出设备。对于I/O bus 包含数据总线、控制总线、地址总线;每一个
    发表于 11-24 09:28 3577次阅读
    输入输出<b class='flag-5'>设备</b><b class='flag-5'>I</b>/<b class='flag-5'>O</b><b class='flag-5'>设备</b>总结

    如何减少器件间通信所用的I/O引脚数实现双向通信

    随着嵌入式系统的小型化趋势,市场对减少器件间通信所用的I/O 引脚数的需求与日俱增。Microchip 开发的UNI/O® 总线满足了这一需求,这一低成本且易于
    发表于 04-27 16:14 8次下载
    如何减少器件间<b class='flag-5'>通信</b>所用的<b class='flag-5'>I</b>/<b class='flag-5'>O</b>引脚数<b class='flag-5'>实现</b>双向<b class='flag-5'>通信</b>

    I/O软件模拟虚拟化和类虚拟化

    的标准化接口。Virtio成为整个问题的焦点:不管是SPDK/vhost、还是vDPA加速,都是围绕着Virtio接口展开。 1 I/O设备
    的头像 发表于 10-13 11:09 2551次阅读

    I/O虚拟化及Virtio接口介绍

    I/O虚拟化是计算机虚拟化最复杂的部分,因为涉及到CPU、操作系统、Hypervisor以及I/O设备的相互配合。
    的头像 发表于 10-26 17:21 3930次阅读

    探究I/O虚拟化及Virtio接口技术(上)

    I/O虚拟化是SmartNIC/DPU/IPU中最核心的部分,AWS NITRO就是从I/O硬件虚拟化开始,逐渐开启了DPU这个新处理器类型的创新。而
    的头像 发表于 04-04 16:54 3734次阅读
    探究<b class='flag-5'>I</b>/<b class='flag-5'>O</b>虚拟化及<b class='flag-5'>Virtio</b>接口技术(上)

    探究I/O虚拟化及Virtio接口技术(下)

    I/O虚拟化是SmartNIC/DPU/IPU中最核心的部分,AWS NITRO就是从I/O硬件虚拟化开始,逐渐开启了DPU这个新处理器类型的创新。而
    的头像 发表于 04-04 17:03 2595次阅读
    探究<b class='flag-5'>I</b>/<b class='flag-5'>O</b>虚拟化及<b class='flag-5'>Virtio</b>接口技术(下)

    VirtIO Networking虚拟网络设备实现架构

    VirtIO 由 Rusty Russell 开发,最初是为了支持自己开发的 lguest Hypervisor,其设计目标是在虚拟化环境下提供与物理设备相近的 I/O 功能和性能
    的头像 发表于 05-08 10:48 1066次阅读
    <b class='flag-5'>VirtIO</b> Networking虚拟网络<b class='flag-5'>设备</b><b class='flag-5'>实现</b>架构

    RT-Thread设备驱动开发之二I/O设备框架

    设备驱动层是一组驱使硬件设备工作的程序,实现了访问硬件设备的功能,它负责创建和注册I/O
    的头像 发表于 10-12 10:26 442次阅读

    Linux I/O 接口的类型及处理流程

    ()、lseek() 等。 网络 I/O 接口:用于网络通信的接口,包括 socket()、connect()、bind()、listen()、accept() 等。 设备
    的头像 发表于 11-08 16:43 833次阅读
    Linux <b class='flag-5'>I</b>/<b class='flag-5'>O</b> 接口的类型及处理<b class='flag-5'>流程</b>