通过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. 概述 本次升 ...
随机推荐
- teprunner测试平台定时任务这次终于稳了
teprunner测试平台已经有一个多月没有更新了,主要原因是定时任务不够稳定,经过反复试错,找到了解决办法,这次终于稳定了. 本文开发内容 作为测试平台而言,定时任务算是必备要素了,只有跑起来的自动 ...
- Spring Cloud 升级之路 - 2020.0.x - 7. 使用 Spring Cloud LoadBalancer (2)
本项目代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们使用 Spri ...
- 重新整理 .net core 实践篇—————配置系统之强类型配置[十]
前言 前文中我们去获取value值的时候,都是通过configurationRoot 来获取的,如configurationRoot["key"],这种形式. 这种形式有一个不好的 ...
- 揭秘有状态服务上 Kubernetes 的核心技术
背景 随着 Kubernetes 成为云原生的最热门的解决方案,越来越多的传统服务从虚拟机.物理机迁移到 Kubernetes,各云厂商如腾讯自研上云也主推业务通过Kubernetes来部署服务,享受 ...
- python发送丁丁消息
import requests import time import hashlib import hmac import base64 import re def SendMessage(messa ...
- MongoDB学习笔记:MongoDB 数据库的命名、设计规范
MongoDB学习笔记:MongoDB 数据库的命名.设计规范 第一部分,我们先说命名规范. 文档 设计约束 UTF-8 字符 不能包含 \0 字符(空字符),这个字符标识建的结尾 . 和 $ ...
- 嵌入式Linux设备驱动程序:用户空间中的设备驱动程序
嵌入式Linux设备驱动程序:用户空间中的设备驱动程序 Embedded Linux device drivers: Device drivers in user space Interfacing ...
- 小白自制Linux开发板 一. 瞎抄原理图与乱画PCB
因为墨云是基于高中物理水平的电路知识来学习.而且此前也就玩过树莓派.Esp8266之类的开发板,水平基础趋近于零,所以在写这个系列的时候抱着记录的心态.还望不足之处还望大佬们指正. <论语> ...
- Win10 下python_appium的Android手机自动化环境搭建
前提: 已经安装好了Java环境,且配置了环境变量 已经安装python3.8.2,已经安装pycham. 一.安装appium_client ,pycham中也需要安装 二.安装node.js(需要 ...
- 6, java数据结构和算法: 栈的应用, 逆波兰计算器, 中缀表达式--> 后缀表达式
直接上代码: public class PolandCalculator { //栈的应用:波兰计算器: 即: 输入一个字符串,来计算结果, 比如 1+((2+3)×4)-5 结果为16 public ...