目录
I/O基本概念
I/O设备与CPU连接图
CPU与I/O设备的交互
访问I/O设备(IO Access)
数据传输(Data Tronhsfer)
I/O设备虚拟化技术
软件辅助全虚拟化
半虚拟化
Virtio协议基本概念
Virtqueue讲解
硬件辅助全虚拟化
I/O直通方式
SR-IOV方式
I/O基本概念
什么是I/O设备
I/O就是输入输出,而I/O设备就是可以将数据输入到计算机,或者将计算机的数据输出到其他设备,属于计算机的硬件设备
I/O设备具体使用什么类型的数据、做什么事情取决于I/O设备本身的功能,不同功能的I/O设备传输数据可能不同(例如:磁盘会将数据持久化、网卡会将数据转发到其它主机)
I/O设备的分类
存储I/O设备(块设备-可以寻址)
也称外存,用来存储设信息
例如:硬盘、光盘、U盘等
人机交互设备(字符设备-不可寻址)
主要就是实现用户与计算机系统之间的通信
在输入输出时通常采用中断驱动的方式
例如:打印机、键盘鼠标等
网络通信设备
主要用于与远程设备进行通信
例如:网卡、调制解调器等
I/O设备与CPU连接图
通常I/O设备并不会直接与CPU进行通信,而是依靠设备控制器通信来实现通信(设备控制器位于计算机本身内部,集成在主板上,包含一些寄存器)
设备控制器的作用
接收设备的CPU指令(CPU的读写指令和参数存储在控制寄存器中)
向CPU报告设备的状态(状态寄存器记录I/O设备的当前状态)
进行数据交换(通过数据寄存器暂存CPU发来的数据和IO设备发来的数据,之后将数据发送给控制寄存器或CPU)
地址识别(通过可寻址存储或队列根据CPU提供的地址来判断CPU要读取的是哪个IO设备)
CPU与I/O设备的交互
在一次完整的CPU与IO设备的交互中,有三个重要的部分
CPU访问I/O设备,CPU与I/O设备进行数据交换,I/O设备将结果返回给CPU
访问I/O设备(IO Access)
访问I/O设备目前主要有两种方式PMIO和MMIO
设备控制器上有一组IO寄存器, CPU通过访问IO寄存器就可以对IO设备进行相应的操作
两种不同的方式其区别就是找到IO寄存器的方式不同
PMIO端口映射IO(Port Mapped IO)
将I/O设备独立看待,使用CPU提供的专用I/O指令访问I/O设备
设备控制器的寄存器上映射了一组IO端口(这些IO端口存储在设备控制器中,不占用内存地址),CPU就可以通过一些特殊的指令来访问这些端口,从而达到访问IO寄存器的效果,最后达到对IO设备进行操作的目的
优点:使用专门的IO指令对端口进行操作,IO指令短,执行速度快
缺点:CPU需要设计单独的指令,实现复杂;不能使用通用的内存访问指令操作
MIMI内存映射IO(Memory Mapped IO)
物理内存和I/O设备共享物理内存地址空间,直接访问内存地址访问I/O设备
将设备控制器上寄存器映射的一组IO端口映射为一些特殊的内存地址,CPU只需要访问这些特殊的内存地址的话则可以达到访问IO端口的效果,也就对设备进行了操作(操作系统内部也不需要执行特殊的指令,而只需要像正常的操作一样访问特殊的内存地址即可对IO设备进行操作)
后续衍生出了PCI设备
PCI设备及其衍生的接口(如PCIE)主要服务于高速I/O设备-网卡和显卡等
计算机既要访问设备管理器的寄存器,也要访问它们的内存
数据传输(Data Tronhsfer)
数据传输目前主要有三种实现方式:传统的标准传输、引入中断的传输、DMA传输
传统的标准交互(程序直接控制的方式)
CPU通过发送IO指令来进行数据传输(以读取数据为例)
CPU通过PMIO或MMIO获得IO设备对应的状态寄存器,然后读取I/O设备的状态,如果是忙碌状态就继续轮询检查状态,如果是空闲状态,则从中数据寄存器中读取数据到CPU中;读到CPU后CPU还需要往内存中写入数据,写完后在执行下一套指令
优点:实现简单
缺点:CPU和I/O设备只能串行化工作,CPU要一直轮巡检查,长期处于忙等的低效状态中
中断驱动的方式
通过允许I/O设备主动打断CPU的运行并请求服务来解放CPU(以读取数据为例)
CPU通过PMIO或MMIO获得IO设备对应的状态寄存器,然后读取I/O设备的状态,如果是忙碌状态则CPU先做自己的任务,当IO设备空闲时便发出中断请求信号,CPU感知到中断后就会把当前正在进行任务的相关计算数据保存下来,优先完成对I/O设备的处理,然后CPU将读取的数据在存入到内存中
优点:CPU和I/O设备可以并行工作,CPU利用率得到提升
缺点:数据在硬件中的移动是通过CPU完成的,仍然会消耗较多的CPU时间
直接存储方式DMA(Direct Memory Access)
以上两种方式,数据在硬件中的移动是通过CPU完成的(CPU读取数据时先得到数据,然后再由CPU存入内存;I/O设备读取数据时也是由CPU作为中间人)
DMA的数据流向是从IO设备直接放入内存,或从内存直接到IO设备,不需要使用CPU作为中间者,直接将CPU从I/O任务中解脱出来
多了DMA控制器模块,CPU将需要读取数据告诉DMA控制器(想要读取数据的那一块,然后把数据读到哪个内存的哪个地址),然后DMA控制器就代替CPU和设备控制器进行交互完成读取的工作,并将读取的数据写入到内存中去,此时CPU就可以去做其他的事情
当DMA控制器得操作执行完成后,将产生中断请求信号,通知CPU读取任务完成
优点:仅在开始和结束需要CPU干涉,CPU和利用率进一步提升
缺点:CPU发出一条指令只能读或写一个或多个连续的数据块;如果读写的数据块不是连续存放的则CPU需要分别发送多条I/O指令,进行多次中断处理
还有一种方式:通道控制方式
通道值得是一种硬件,可以理解为低配版的CPU
通道根据CPU的指令执行响应程序,只有完成一组数据块的读写后才会发出中断信号让CPU干预(也是直接有设备→内存或内存→设备)
CPU的干预频率最低
实现方式
CPU干预频率
每次I/O传输的单位
数据流向
程序直接控制方式
极高
字
设备→CPU→内存
内存→CPU→设备中断驱动方式
高
字
设备→CPU→内存
内存→CPU→设备DMA方式
中
块
设备→内存
内存→设备通道控制方式
低
一组块
设备→内存
内存→设备
I/O设备虚拟化技术
虚拟化架构:虚拟化的基础知识-CSDN博客
软件辅助全虚拟化
核心思想
将I/O设备以一个虚拟设备的形式暴露给虚拟机,虚拟机操作系统内部看来他正在使用一个真实的物理设备
VMM截获虚拟机驱动程序对设备的访问,并进行设备模拟;VMM的设备模拟器需要仔细研究现实设备的接口定义和内部设计规范,然后通过软件的方式完全模拟I/O设备,显示设备具有哪些资源,设备模拟器就需要呈现出同样的资源
访问设备
CPU通过PCI总线与I/O设备连接,在CPU的视角下,每个设备就是一个PCI设备,而每一个PCI设备都有一组重要的寄存器,叫做基址寄存器,软件虚拟化是通过VMM模拟基地址寄存器给虚拟机使得虚拟机能够找到I/O设备
访问拦截
虚拟机CPU访问虚拟I/O设备的两种形式PMIO和MMIO
PMIO和MMIO找到设备对应的IO端口或内存地址都是通过基址寄存器完成的
PMIO访问拦截
CPU通过特殊指令访问IO端口,这些端口可以由VMM来屏蔽掉,一旦CPU要访问这些端口,CPU就会触发异常,退出虚拟机,由VMM接管,此时VMM就可以截获到这次IO请求
MIMO访问拦截
CPU通过内存特殊地址来访问IO端口,该内存特殊地址为GPA,我们需要将其映射为HPA;而GPA→HPA的映射是由VMM建立的,当虚拟机触发MMIO来访问这些IO特殊地址时,触发异常产生VM exit交给VMM处理,这时VMM就可以感知到虚拟机对IO设备的访问了,并拦截了此次访问请求
设备模拟
需要VMM来模拟设备的真实行为了,不同的IO设备类型模拟不同的行为
在VMM层面,这些模拟的设备是一个基于本地文件系统的一个文件,而虚拟机对于整个磁盘的读写在经过VMM层就转化为了对这个文件某一位置的读写,在此过程中就涉及到数据传输(DMA为例子)
VMM内部需要模拟DMA的功能,虚拟机的设备驱动会告诉该DMA要读取数据的信息(虚拟机告知DMA的是GPA地址,需要通过内存虚拟化将该GPA转为HPA地址),然后由VMM来模拟这个行为将数据从文件中读取出来写入到目标地址中
当DMA数据传输完成之后,VMM产生中断(通过为虚拟机的vCPU注入一个中断),告知虚拟机内部这次IO请求的完成;然后由虚拟机内部的中断机制来完成IO请求剩余的中断处理
此方式的特点
虚拟机操作系统原有的驱动程序无需修改就可以驱动虚拟设备
对虚拟机来说非常透明,无需考虑底层硬件的情况
软件模拟效率较低,并且虚拟机发送出来的I/O请求需要虚拟机和VMM之间进行多次交互,产生巨大开销
以KVM架构实现软件辅助全虚拟化的架构
虚拟机发送I/O请求到虚拟机的设备驱动
设备驱动发送的I/O设备操作请求被KVM模块中的I/O操作捕获代码捕获(不会直接发到物理设备上)
KVM的I/O操作模块捕获到I/O请求后,把请求进行处理,将请求信息存放到I/O共享页
QEMU程序从I/O共享页读取信息后,交由硬件模拟代码来模拟出本次I/O操作,然后将其转发给硬件,硬件将结果返回给QEMU程序
QEMU程序将将结果放回到I/O共享页,并通知KVM模块中的I/O操作捕获代码
KVM模块的捕获代码读取I/O共享页中的操作结果,将结果返回给虚拟机
半虚拟化
Virtio协议基本概念
为什么提出Virtio协议
Virtio很好的定义了一套半虚拟化的架构,但并不是唯一的半虚拟化IO的技术,它的主要意义是简化和统一半虚拟化I/O的标准
Virtio半虚拟化作为当前KVM平台上主流的半虚拟化IO协议,在设备的资源高效利用和保持性能之间做到了一个较好的平衡;,既能够利用半虚拟化的特点,在数据传输层面极大的提高IO的效率,又能够保持前后端的良好兼容
其他半虚拟化协议
Xen有自己的一套完整的半虚拟化IO的实现方案、VMware也有自己的一套半虚拟化实现方案
Virtio协议的概念
virtio定义了一套典型的前后端架构,通过在虚拟机安装前端驱动(Front-end driver),在VMM安装后端驱动(Back-end);前端将请求通过VMM提供的通信机制直接发给后端,后端处理完请求后再发回通知给前者,使得前后端直接通信
前后端之间的通信可以分为两个层面
控制层面
通过控制层面使得前后端在通信之前在某方面达成一致,还会做一些对于数据层面的配置(例如CPU发现设备、配置IO设备、管理IO设备等相关操作)
例如:由于软件不断更新,前后端之间的版本可能不一致,在新的版本中可能会有新的特性;为了达成一致,后端会将自己的特性集合发给前端,前端也会将这些特性与自己的特性进行对比,最后返回两者的交集,这样就可以确保在特性上达成一致
数据层面
数据层面是为虚拟机和IO设备进行数据传输所服务的;可以对虚拟机和设备之间IO数据传输的相关数据与传输方式进行设定
在数据层面中定义了一个virtqueue数据传输结构来供前端和后端进行数据上的通信
Virtqueue讲解
什么是Virtqueue
Virtqueue是一个队列的抽象模型,前端驱动负责向virtqueue中插入一个个数据buffer,后端则负责处理这些buffer;每个buffer可以由一段或多段不连续的数据空间链接而成,每段数据空间可以由不同的读写权限用于不同的用途(即buffer的具体结构与IO设备类型相关)
virtqueue模型如何实现前后端通信
virtqueue需要支持5个接口来实现数据在虚拟机和宿主机之间的传输
add_buf接口:向virtqueue中插入一个待处理的buffer
kick接口: 通知宿主机由新的buffer加入
get_buf接口: 从virtqueue中获取一个宿主机处理完成的buffer
disable_cb和enable_cp接口:IO设备驱动中的关中断和开中断,用于设置在宿主机处理完一个buffer后是否会被调用
virtio_ring的作用以及组成
Virtqueue是数据传输的抽象模型,而virtio_ving(也称为ving)是这个模型的一种高效实现方式,而ving主要由以下三个部分组成
描述符表项
首先virtqueue会给每个buffer分配一个描述符(描述buffer的地址、长度等),并且也会维护一个全局的表来记录这些描述符,这个表中包含描述符所描述的特性(也有可能多个buffer连成一个链表)
Available ring队列
这个队列主要就是来表示在描述表里面有哪些请求是需要被处理的,需要将其在描述表中对应的序列号放入此队列(如果序号之间存在链接,则只放第一个序号)
Used ring队列
后端处理完成后会将处理好的请求放到该队列中,这个队列的格式和Available Ring是差不多的,也是把相应的描述符在描述表中对应的序号填写进去(如果后端往buffer里面写过数据,它同样会带上她在buffer里面写入数据的长度)
工作流程
假如前端发送了一个IO读取的请求,则前端通过add_buf接口在virtqueue中插入一个buffer,并且接下来会往描述符表项中写入关于这一个buffer的描述符(包含地址、长度、序号等信息)
然后此buffer描述符对应的序列号加入到Available Ring队列,此时前端就会通过virtqueue提供的kick接口来告知后端设备这些请求是需要处理的
然后后端设备将会从Available Ring队列里面读取请求来处理(当然这个kick也会涉及到一次vm-exit)
后端处理完成后会将处理好的请求放到Used Ring队列(也是把相应的描述符在描述表项中的对应序号填写进去),然后会以一个正常的中断形式注入给虚拟机,告知其内部I/O操作完成
然后前端通过get_buf接口从virtqueue中的Used Ring队列获取宿主机处理完成的buffer
特点
需要修改虚拟机和宿主机操作系统内核相应的代码来满足相应要求,兼容性较低
省略了我们在发送I/O前先进行VMM捕获的过程,使用效率提升
支持前端在Available Ring中设置让后端知道什么时候能够触发一个中断,也可以在后端Used Ring中设置前端的下一次Kick是什么时候
以KVM架构实现半虚拟化的架构
不过常用的半虚拟化架构为Xen架构
硬件辅助全虚拟化
目前常用的有两种方式
I/O直通方式(设备透传方式)、SR-IOV方式
I/O直通方式
核心思想
虚拟机真正直接使用到了物理设备,给物理服务器安装多个I/O设备,将一个物理I/O设备直通给虚拟机使用,使得虚拟机操作系统可以直接操作这个I/O设备,尽量避免触发VM exit来让VMM介入(最常用的就是网卡--直通网卡)
I/O直通技术需要硬件支持才可以完成,首选的是Inter的VT-d技术
设备访问
我们要知道PMIO和MMIO找到设备对应的IO端口或内存地址都是通过PCI设备的基址寄存器完成的
因此在直通的情况下,虽然物理设备直接存在,但是不能直接把物理设备的基址寄存器暴露给虚拟机,因为在设备透传给虚拟机之前,该I/O设备还需要挂载给物理主机,因此该I/O设备的基址寄存器实际上是宿主机配置的而不是虚拟机,如果直接将I/O的基址寄存器直接暴露给虚拟机,可能会存在基址寄存器冲突,因此在物理设备透传的情况下,我们也需要模拟寄存器给虚拟机
访问拦截
PMIO方式的访问拦截
虚拟机的基址寄存器是不同于物理机的基址寄存器的,因此他们的IO端口可能就是不同的,所以当虚拟机访问自己的IO端口时VMM需要将其截获,并且将这个IO请求转发给物理设备真实的IO端口,此时就会涉及到依次VM exit
MMIO方式的访问拦截
MMIO下虚拟机访问IO设备直接访问的就是一个内存地址,这个内存地址为GPA,然后通过内存虚拟化将GPA转为HPA;对于IO设备也是需要在VMM上建立IO GPA到IO HPA的映射关系,使得虚拟机访问IO设备就可以直接通过映射表来完成
数据传输
DMA重映射
虚拟机内部的设备驱动是期待这个设备支持DMA功能的
在DMA的视角下,它只知道虚拟内存的地址空间,它就会往虚拟地址中读写数据,但是实际上这个内存地址是错误的,应该写入的地址是物理机内存地址
因此就需要做到DMA的重映射,这个功能是由介于设备控制器和内存之间的一个模块(叫做IOMMU实现的,该模块属于CPU的功能)
IOMMU介绍
IOMMU是一个内存管理单元,主要针对外设访问系统内存时进行内存管理
IOMMU的主要功能包括DMA Remapping(地址重映射)和Interrupt Remapping(中断重映射)
对于DMA Remapping,IOMMU可以将一个设备访问地址转换为存储器地址(对于虚拟机的情况来说就是可以实现GPA与HPA的转换)
即:在IOMMU中会有一个表项,这个表是通过一个一个的PCI设备来作为索引的,并且能够导向虚拟机的信息(包含IO GPA到IO HPA的映射信息),这个索引到虚拟机信息的映射是由负责分配虚拟机内存的VMM建立的
具体IOMMU工作原理
在整个PCI架构中,每个设备都是有自己的一个唯一的标识符,类似于身份证,我们叫做Src ID,当设备通过这个PCI总线进行通信的时候,会携带上自己的Src ID,而I/O设备和内存之间通信也一样
当DMA发送请求时会被IOMMU给截获并找到对应PCI设备的Src id,然后通过IOMMU的映射就可以将这个DMA控制器所访问的GPA内存地址转换为HPA,从而DMA的操作也就可以访问到一个正确的内存地址了
当DMA完成操作之后会产生中断信息,告诉虚拟机IO操作完成
特点
这种方式从性能上来说是最优先的,但这种方式需要比较多的硬件资源
这种方式一旦一个设备被虚拟机占用了,其他虚拟机就无法使用该设备
SR-IOV方式
设备透传无法达到单个设备共享给多个虚拟机使用的目的,因为IO设备是依据挂载给单个主机使用而设计的;于是可以在IO设备设计时就考虑到将其运行在虚拟化环境中的,使得一个I/O设备支持共享给多个虚拟机来使用,这就是SR-IOV技术
SR-IOV的基本概念
在宿主机上挂载一个支持SR-IOV的高性能网络设备,需要CPU支持IOMMU
SR-IOV是将设备的虚拟化放到了硬件层面来实现,从而减少了软件层面的开销
SR-IOV单独引入了两种软件实体功能
Physical Function(PF)
PF包含轻量级的PCIE功能, 一个PF能够作为一个PCIE设备来供这个宿主机所配置
一个SR-IOV设备上可能会有多个PF,本次只考虑单个PF的情况
作用:为虚拟机提供设备访问功能和全局贡献资源配置的功能
VF(Virtual Function)
VF也包含轻量级的PCIE功能,我们可以将这个VF透传给一个虚拟机所使用,因此多个VF便可以拓展给不同的虚拟机,这样就可以达到将整个SR-IOV设备共享给多个虚拟机的目的
在一个PF上可以衍生出来多个VF,一个PE最多有64000个与其相关联的VF
作用:为虚拟机提供接口、进行数据的发送与接收功能、与PE通信完成全局相关配置
具体的工作流程
虚拟机和VF设备的交互与IO直通的交互类似,只不过SR-IOV是多个虚拟机共享同一个I/O设备
既然VF可以作为一个正常的PCIE设备透传给虚拟机使用,那是否会遇到在使用一个正常的物理设备透传给这个虚拟机所需要的问题(也就是IO直通所遇到的问题)?
答案是会遇到
我们知道IO直通场景下有一个重要的阶段IO数据的传输,当DMA请求时,需要通过IOMMU模块将GPA翻译为HPA来完成内存的读写
VF也是一样的,它支持DMA功能,也会通过IOMMU将GPA转为HPA,从而找到宿主机上正确的HPA,将IO相关的数据写入到目标内存地址中
因此一个VF设备也会需要有一个自己全局唯一的设备标识符(Src ID);IOMMU就是通过这个Src ID找到这个VF挂载的虚拟机,找到这个虚拟机的IO内存地址的映射,从而找到这个GPA所映射的HPA
特点
只是SR-IOV设备本身是基于虚拟化环境设计的,因此他可以做一些专门针对虚拟化环境的优化(例如可以增加一个本地的高速缓存存放GPA到HPA的映射,从而在某些场景下就省去了查询IOMMU的功夫)
建立了基于PF&VF的标准
性能最高,几乎和直接使用硬件I/O设备没有区别