通过MMIO的方式实现VIRTIO-BLK设备(一)
背景知识
什么是VIRTIO
使用完全虚拟化,Guest不加任何修改就可以运行在任何VMM上,VMM对于Guest是完全透明的。但每次I/O都将导致CPU在Guest模式与Host模式间切换,在I/O操作密集时,这个切换是影响虚拟机性能的一个重要因素。对于通过软件方式模拟的虚拟化而言,完全可以制定一个更加高效简洁地适用于软件模拟环境下的驱动和模拟设备交互的标准,于是Virtio诞生了。与完全虚拟化相比,使用Virtio标准的驱动和模拟设备的交互不再使用寄存器等传统的I/O方式,而是采用了Virtqueue的方式传输数据。这种设计降低了设备模拟器实现的复杂度,I/O不再受数据总线宽度、寄存器宽度等因素的影响,一次I/O传递的数据量不受限制,减少了CPU在Guest模式与Host模式之间的切换,提高了虚拟化的性能。
作为一个统一的标准,越来越多的操作系统(例如Linux与Windows)已经提供了对Virtio的支持。
本文将根据Virtio1.0文档讲述VIRTIO的实现。
Dirver 与 Device
在VIRTIO中,Driver实现在虚拟机,是VIRTIO的前端;Device实现在虚拟机监控器,是VIRTIO的后端。
描述符表
Virtqueue是VIRTIO中数据传输的载体,是VIRTIO的核心部分。Virtqueue主要包括三个部分,分别是描述符表(Descriptor Table),可用描述符区域(Available Ring),已用描述符区域(Used Ring)。
VIRTIO要求描述符表,可用描述符表,已用描述符表分别在GPA上连续。
这三个表可以通过下图表示:
从左到右依次是,可用描述符表,描述符表,已用描述符表。

描述符表
描述符表是Virqqueue的核心,它包括Queue Size个描述符(Queue Size由driver决定,必须是2的指数,它的值保存在QueueSize寄存器上)。
每个描述符会指向一块共享内存,如果这块内存是驱动写给设备的数据,则称这个描述符为out类型的,如果这块内存是设备写给驱动的数据,则称这个描述符为in类型。
描述符并不是单独存在的,它们可以通过指针组成描述符链,一个描述符表中会有多条描述符链,一条描述符链记录一次I/O事件。

描述符有4个字段,如上图所示。描述符通过addr指向一块保存有I/O数据的共享内存,需要注意的是addr保存的是GPA,当后端需要根据通过addr读写该块共享内存时,需要视虚拟机监控器的实现将GPA转换成HVA或者HPA。len表示该块共享内存的长度。flags标识了描述符的属性,当flags * F_NEXT成立,则描述符可以通过next指向下一个描述符;当flags * F_WRITE成立,则这个描述符属于in类型,否则则是out类型;当flags * F_INDIRECT成立时,共享内存上将不是直接保存数据,而是保存一连串描述符。next指针则指向描述符链中的下一个描述符。
可用描述符表
driver将数据写入描述符记录的共享内存后,需要让device知道哪些描述符可以消费(可用)。可用描述符负责完成这个任务。

可用描述符中的ring是一个数组,因virtqueue中最多可能有Queue Size可用描述符链,ring的大小是Queue Size。ring中每个元素都记录了对应的描述符链的第一个描述符的ID,因此ring中一个元素对应一条描述符链,也即对应一次I/O事件。可用描述符中idx变量记录的是driver下一个填充的可用描述符,与之对应的是device将在变量last_avail_idx中记录上一个处理完的可用描述符,因此在last_avail_idx到idx之间是等待device处理的可用描述符。
已用描述符表
device将已经处理好的IO请求对应的描述符记录在已用描述符中,从这里可以看出,可用,已用这两个概念都是对device而言的。一个需要注意的点是,可用描述符和已用描述符都是指向描述符链,它们只是说明该条描述符链的状态,并不是代表描述符链的in/out类型。

与可用描述符不同的是,已用描述符的数组中每个元素的大小是8byte,它不仅记录了描述符链第一个描述符的ID,还记录了device向描述符链中写入的byte数。已用描述符通过idx和last_used_idx记录了等待driver回收的描述符链。idx由设备维护,表示设备下一个处理完的描述符链将记录在已用描述符表中的位置,last_used_idx由驱动维护,记录的是驱动上一个回收的描述符链在已用描述符表中的位置。
VIRTIO MMIO寄存器(部分
在MMIO实现VIRTIO的情况下,每个VIRTIO设备都有一个MMIO REGION。这个REGION在设备树中的声明如图:

上图表示GPA 0x1e000 到 0x1e200 的地址段是这个virtio_block设备的MMIO REGION。这个REGION中分布着VIRTIO MMIO寄存器。
42是这个virtio设备对应的中断号,注意42需要加上SPI中断的基础值:32,因此这个virtio driver实际上能够识别的中断号是74。
一些重要的MMIO 寄存器如下:
DeviceFeatures & DeviceFeaturesSel
设备通过DeviceFeatures寄存器告诉驱动设备支持的一些机制,比如VIRTIO_RING_F_INDIRECT_DESC这个bit就是告诉driver:device支持virtqueue通过indirection扩大共享内存区域。driver只能够在device提供的机制上工作,不能够在device没有提供该机制的情况下运行对应的代码。由于DeviceFeatures的区域要大于4bytes,driver需要通过DeviceFeaturesSel寄存器用查看DeviceFeatures的部分bits。
DriverFeatures & DriverFeaturesSel
驱动通过DriverFeatures寄存器告诉设备,驱动支持了设备的哪些机制,DriverFeaturesSel则用于设备查看DriverFeatures。
QueueSel
对于某些virtio设备,比如virtio-net,virtio-console会包括多个virtqueue。为了让设备知道该对在哪条virtqueue上进行处理,driver会通过QueueSel寄存器告诉驱动后续的操作是在哪条virtqueue进行的。
QueueReady
driver会通过写QueueReady寄存器通知设备,当前virtqueue已经初始化好了,设备可以通过读描述符寄存器来获得virtqueue的地址。
QueueNotify
当driver准备了新的可用描述符时,会通过写QueueNotify寄存器通知device进行处理。
InterruptStatus
Virtio设备可以通过发送中断通知虚拟机,每个virtio设备有一个对应的中断号(这个中断号在设备树中声明),虚拟机在收到中断后,会根据中断号找到对应的driver,driver则需要通过InterruptStatus寄存器搞清楚产生这次中断的事件是什么,比如bit 0表示已用描述符更新,bit 1表示设备配置空间更新。
QueueDescLow & QueueDescHigh
driver通过写这两个寄存器告诉device描述符表的GPA。由于每个MMIO寄存器只有32个bit,因此需要两个寄存器。
QueueAvailLow & QueueAvailHigh
driver通过写这两个寄存器告诉device可用描述符表的GPA。
QueueUsedLow & QueueUsedHigh
driver通过写这两个寄存器告诉device已用描述符表的GPA。
Config
Config不是一个寄存器,而是一个区域,这个区域由device进行配置,每种device会有不一样的配置区域。
下图展示的就是block设备配置空间的数据结构。
参考资料
《深度探索Linux系统虚拟化:原理与实现》
《Virtual I/O Device Version 1.0》
《Linux虚拟化KVM-Qemu分析(十一)之virtqueue》
https://github.com/minosproject/minos/
下一期将介绍实现VIRTIO-BLK设备时,虚拟机image,rootfs,dtb文件的制作
通过MMIO的方式实现VIRTIO-BLK设备(一)的更多相关文章
- Virtio SCSI设备介绍
Qemu的存储栈 在KVM虚拟化环境中,当客户机的内核存储系统像在物理机上一样通过页缓存.文件系统.通用块设备层运行到实际设备驱动时,这时驱动对设备寄存器的访问会触发CPU从客户机代码切换到物理机内的 ...
- 每日技术总结:vue router传参方式,js获取设备高度
今天貌似没什么问题,23333…… 1.vue router 路由传参的方式 应用情景:从分类页(category.vue)进入商品列表页(list.vue),需要传递商品分类id(catId),商品 ...
- Tcp方式采集CNC兄弟设备数据
先说下为了采集CNC兄弟设备的数据可谓是一波三折. 因为首次接触brother设备(CNC)是直接在设备上设置IP.用户名.密码,然后直连PC,用Ftp可以查看和下载CNC brother设备里的数据 ...
- 三种方式设置特定设备UWP XAML view
开发者可以设置UWP特定设备xaml view,在桌面,手机,Iot,这个对于设置对不同设备的不同屏幕有用.我们可以使用RelativePanel,VisualStateTriggers,但是这样我们 ...
- 基于Linux的USB 主/从设备之间通讯的三种方式
转载:http://archive.eet-china.com/www.eet-china.com/ART_8800323770_617693_TA_eda530e7.HTM 随着简单易用的USB接口 ...
- 计算机组成原理——I/O接口以及I/O设备数据传送控制方式
接口可以看作是两个部件之间交接的部分.硬件与硬件之间有接口,硬件与软件之间有接口,软件与软件之间也有接口. 这里我们所说的I/O接口,一边连接着主机,一边连接着外设. I/O接口的功能 I/O接口的基 ...
- 5-(微信小程序篇)关于WiFi模块配网以后利用小程序绑定设备,绑定方式说明
https://www.cnblogs.com/yangfengwu/p/11625189.html 众所周知:使用微信Airkiss 只能给设备配网,并不能够获取设备的MAC地址信息,但是我在 ht ...
- 2018-8-10-三种方式设置特定设备UWP-XAML-view
title author date CreateTime categories 三种方式设置特定设备UWP XAML view lindexi 2018-08-10 19:16:52 +0800 20 ...
- iNeuOS工业互联网操作系统,发布实时存储方式:实时存储、变化存储、定时存储,增加设备振动状态和电能状态监测驱动,v3.6.2
目 录 1. 概述... 1 2. 平台演示... 2 3. 存储方式... 2 4. 设备状态和用电状态监控驱动... 3 1. 概述 本次升 ...
随机推荐
- 使用原生JS,实现鼠标点击爱心效果 !!!
使用原生JS,实现鼠标点击爱心效果 !!! 引言: 在很多时候我们都需要实现鼠标点击出现图案或者文字这样的效果,对于用户而言,这样的体验是很极致的.其实实现起来也很简单,下面一起来学习一下吧.文末附上 ...
- unity lua require dofile loadfile 区别
oadfile,加载文件,编译文件,并且返回一个函数,不运行 dofile其实就是包装了Loadfile,根据loadfile的返回函数运行一遍 require加载文件的时候,不用带目录,有lua自己 ...
- 重新整理 .net core 实践篇————配置系统——军令(命令行)[六]
前言 前文已经基本写了一下配置文件系统的一些基本原理.本文介绍一下命令行导入配置系统. 正文 要使用的话,引入Microsoft.extensions.Configuration.commandLin ...
- 四:net core(.NET 6)使用Filter过滤器拦截webapi方法
Filter的基本用法 代码在最下方 使用filter过滤器,来实现拦截接口信息.咱们先新建一个项目,在原有的webapi上面,选择添加项目,添加一个类库项目: 我起个名字,就叫 Wsk.Core.F ...
- Eclipse从SVN中检出项目缺少Jar包的问题
Eclipse从SVN中检出项目缺少Jar包的问题
- 目标检测coco数据集点滴介绍
目标检测coco数据集点滴介绍 1. COCO数据集介绍 MS COCO 是google 开源的大型数据集, 分为目标检测.分割.关键点检测三大任务, 数据集主要由图片和json 标签文件组成. c ...
- CUDA C编程接口技术分析
CUDA C编程接口技术分析 编程接口 CUDA C为熟悉C编程语言的用户提供了一个简单的路径,可以方便地编写程序供设备执行. 它由C语言的最小扩展集和运行库组成. 核心语言扩展已经引入:cuda c ...
- Jmeter(五十一) - 从入门到精通高级篇 - jmeter之运动战(详解教程)
1.简介 运动战是一种军事作战方式,依托较大的作战空间来换取时间移动兵力包围敌方,以优势兵力速战速决,运动战的运用归为这样一段话"避敌主力,诱敌深入,集中优势兵力逐个击破".今天宏 ...
- 十、使用Varnish加速Web
使用Varnish加速Web 构建Web服务器(web1) [root@web1 ~]# yum -y install httpd [root@web1 ~]# systemctl start ...
- 理解Spring:IOC的原理及手动实现
Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架.也是几乎所有Java工作者必须要掌握的框架之一,其优秀的设计思想以及其代码实现上的艺术也是我们需要掌握的. ...