标签(空格分隔): KVM算法
上一篇文章讲到了网络IO虚拟化,做为另一个重要的虚拟化资源,块设备IO的虚拟化也是一样很是重要的。同网络IO虚拟化相似,块设备IO也有全虚拟化和virtio的虚拟化方式(virtio-blk)。现代块设备的工做模式都是基于DMA的方式,因此全虚拟化的方式和网络设备的方式接近,一样的virtio-blk的虚拟化方式和virtio-net的设计方式也是同样,只是在virtio backend端有差异。vim
如上图所示,咱们把块设备IO的流程也看作一个TCP/IP协议栈的话,从最上层提及。缓存
Page cache层,这里若是是非直接IO,写操做若是在内存够用的状况下,都是写到这一级后就返回。在IO流程里面,属于writeback模式。 须要持久化的时候有两种选择,一种是显示的调用flush操做,这样此文件(以文件为单位)的cache就会同步刷到磁盘,另外一种是等待系统自动flush。安全
VFS,也就是咱们一般所说的虚拟文件系统层,这一层给咱们上层提供了统一的系统调用,咱们经常使用的create,open,read,write,close转化为系统调用后,都与VFS层交互。VFS不只为上层系统调用提供了统一的接口,还组织了文件系统结构,定义了文件的数据结构,好比根据inode查找dentry并找到对应文件信息,并找到描述一个文件的数据结构struct file。文件实际上是一种对磁盘中存储的一堆零散的数据的一种描述,在Linux上,一个文件由一个inode 表示。inode在系统管理员看来是每个文件的惟一标识,在系统里面,inode是一个结构,存储了关于这个文件的大部分信息。这个数据结构有几个回调操做就是提供给不一样的文件系统作适配的。下层的文件系统须要实现file_operation的几个接口,作具体的数据的读写操做等。网络
struct file_operations { //文件读操做 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //文件写操做 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); //文件打开操做 int (*open) (struct inode *, struct file *); };
再往下就是针对不一样的文件系统层,好比咱们的ext3,ext4等等。 咱们在VFS层所说的几个文件系统须要实现的接口,都在这一层作真正的实现。这一层的文件系统并不直接操做文件,而是经过与下层的通用块设备层作交互,为何要抽象一层通用块设备呢?咱们的文件系统适用于不一样的设备类型,好比多是一个SSD盘又或者是一个USB设备,不一样的设备的驱动不同,文件系统没有必要为每一种不一样的设备作适配,只须要兼容通用快设备层的接口就能够。数据结构
位于文件系统层下面的是通用快设备层,这一层在程序设计里面是属于接口层,用于屏蔽底层不一样的快设备作的抽象,为上层的文件系统提供统一的接口。架构
通用快设备下层就是IO调度层。用以下命令能够看到系统的IO调度算法.函数
➜ ~ cat /sys/block/sda/queue/scheduler noop deadline [cfq]
noop,能够当作是FIFO(先进先出队列),对IO作一些简单的合并,好比对同一个文件的操做作合并,这种算法适合好比SSD磁盘不须要寻道的块设备。oop
cfq,彻底公平队列。此算法的设计是从进程级别来保证的,就是说公平的对象是每一个进程。系统为此算法分配了N个队列用来保存来自不一样进程的请求,当进程有IO请求的时候,会散列到不一样的队列,散列算法是一致的,同一个进程的请求老是被散列到同一个队列。而后系统根据时间片轮训这N个队列的IO请求来完成实际磁盘读写。
deadline,在Linux的电梯调度算法的基础上,增长了两个队列,用来处理即将超时或者超时的IO请求,这两个队列的优先级比其余队列的优先级较高,因此避免了IO饥饿状况的产生。
块设备驱动层就是针对不一样的块设备的真实驱动层了,块设备驱动层完成块设备的内存映射并处理块设备的中断,完成块设备的读写。
块设备就是真实的存储设备,包括SAS,SATA,SSD等等。块设备也可能有cache,通常称为Disk cache,对于驱动层来讲,cache的存在是很重要的,好比writeback模式下,驱动层只须要写入到Disk cache层就能够返回,块设备层保证数据的持久化以及一致性。
一般带有Disk cache的块设备都有电池管理,当掉电的时候保证cache的内容可以保持一段时间,下次启动的时候将cache的内容写入到磁盘中。
应用层的读写操做,都是经过系统调用read,write完成,由Linux VFS提供的系统调用接口完成,屏蔽了下层块设备的复杂操做。write操做有直接IO和非直接IO之分(缓冲IO),非直接IO的写操做直接写入到page cache后就返回,后续的数据依赖系统的flush操做,若是在flush操做未完成的时候发生了系统掉电,那可能会丢失一部分数据。直接IO(Direct IO),绕过了page cache,数据必须达到磁盘后才返回IO操做完成。
对于I/O的读写流程,逻辑比较复杂,这里以写流程简单描述以下:
如上图所示,在初始化IO设备的时候,会为IO设备分配一部分物理内存,这个物理内存能够由CPU的MMU和链接IO总线的IOMMU管理,做为共享内存存在。以一个读取操做为例子,当CPU须要读取块设备的某个内容的时候,CPU会经过中断告知设备内存地址以及大小和须要读取的块设备地址,而后CPU返回,块设备完成实际的读取数据后,将数据写入到共享的内存,并以中断方式通知CPU IO流程完成,并设置内存地址,接着CPU直接从内存中读取数据。
写请求相似,都是经过共享内存的方式,这样能够解放CPU,不须要CPU同步等待IO的完成而且不须要CPU作过多的运算操做。
由于块设备IO的虚拟化须要通过两次IO协议栈,一次Guest,一次HV。因此须要把块设备IO协议栈说的很具体一点。
至此,Linux块设备的IO层就基本介绍完整了,以上内容也只是作一个简单的介绍,这部分的内容能够很深刻的去了解,在此限于篇幅限制,就不作过多介绍了。
块设备的全虚拟化方式和网络IO的DMA设备虚拟化方式相似,这里就不过多介绍了,主要介绍一下virtio-blk。
如上图所示,块设备IO的虚拟化流程和网络IO的流程基本一致,差异在于virtio-backend一段,virtio-net是写入到tap设备,virtio-blk是写入到镜像文件中。
块设备IO的流程须要通过两次IO协议栈,一次位于Guest,一次位于HV。当咱们指定virtio的cache模式的时候,实际上指定的是virtio-backend(下面简称v-backend)写入HV块设备的方式。
在虚拟化层次来看,Guest对于这几种Cache模式是没有感知的,也就是不管Cache模式是怎样,Guest都不会有所谓的绕过Guest的Page cache等操做,Virtio-front模拟的是驱动层的操做,不会涉及到更上层的IO协议栈。
如上图所示,蓝色表示 writethrough,黄色表示 none,红色表示 writeback。其中虚线表示写到哪个层次后write调用返回。
cache=writethrough (蓝色线)
表示v-backend打开镜像文件并写入时候采用非直接IO+flush操做,也就是说每次写入到Page cache并flush一次,直到数据被真实写入到磁盘后write调用返回,这样必然会致使数据写入变慢,可是好处就是安全性较高。
cache=none (黄色线)
cache为none模式表示了v-backend写入文件时候使用到了DIRECT_IO,将会绕过HV的Page cache,直接写入磁盘,若是磁盘有Disk cache的话,写入Disk cache就返回,此模式的好处在于保证性能的前提下,也能保证数据的安全性,在使用了Disk cache电池的状况下。可是对于读操做,由于没有写入HV Page cache,因此会有必定性能影响。
cache=writeback (红色线)
此模式表示v-backend使用了非直接IO,写入到HV的Page后就返回,有可能会致使数据丢失。
块设备IO的虚拟化方式也是统一的virtio-x模式,可是virtio-blk须要通过两次IO协议栈,带来了没必要要的开销。前面的铺垫都是为了介绍三种重要的cache模式。