一、IO虚拟化的分类 前端
(1)全虚拟化:宿主机截获客户机对I/O设备的访问请求,而后经过软件模拟真实的硬件。这种方式对客户机而言很是透明,无需考虑底层硬件的状况,不须要修改操做系统。 后端
QEMU模拟I/O的状况下,当客户机中的设备驱动程序(device driver)发起I/O操做请求之时,KVM模块中的I/O操做捕获代码会拦截此次I/O请求,而后通过处理后将本次I/O请求的信息存放到I/O共享页,并通知用户控件的QEMU程序。QEMU模拟程序得到I/O操做的具体信息以后,交由硬件模拟代码来模拟出本次的I/O操做,完成以后,将结果放回到I/O共享页,并通知KVM模块中的I/O操做捕获代码。最后,由KVM模块中的捕获代码读取I/O共享页中的操做结果,并把结果返回到客户机中。固然,这个操做过程当中客户机做为一个QEMU进程在等待I/O时也可能被阻塞。函数
另外,当客户机经过DMA(Direct Memory Access)【DMA外接设备能够不用CPU干预,直接把数据传输到内存的技术,尽可能减小CPU干预的输入/输出操做方式。(使用连续物理内存,kmalloc分配)。不然外设一有数据就要中断通知CPU,CPU去读,若是频繁就时间浪费在处理中断,IO速度慢】访问大块I/O之时,QEMU模拟程序将不会把操做结果放到I/O共享页中,而是经过内存映射的方式将结果直接写到客户机的内存中去,而后经过KVM模块告诉客户机DMA操做已经完成。 性能
- 优势是能够模拟出各类各样的硬件设备;
- 缺点是每次 I/O 操做的路径比较长,须要屡次上下文切换、屡次数据复制和VM-Entry/Exit,性能较差。
(2)半虚拟化:经过前端驱动/后端驱动模拟实现I/O虚拟化。客户机中的驱动程序virtio-blk/net/pci/scsi为前端,宿主机提供的与客户机通讯的驱动程序vhost为后端。先后端驱动经过 virtio-ring 直接通讯,绕过了通过 KVM 内核模块的过程,达到提升 I/O 性能的目的。 spa
- 优势快。vring实现了环形缓冲区(ring buffer),用于保存前端驱动和后端处理程序执行的信息,而且它能够一次性保存前端驱动的屡次I/O请求,而且交由后端去动去批量处理,最后实际调用宿主机中设备驱动实现物理上的I/O操做,这样作就能够根据约定实现批量处理而不是客户机中每次I/O请求都须要处理一次,从而提升客户机与hypervisor信息交换的效率。
- 缺点:须要客户机中virtio相关驱动的支持(较老的系统默认没有自带这些驱动,Windows系统中须要额外安装virtio驱动),故兼容性较差,并且I/O频繁时的CPU使用率较高
(3)IO透传:直接把物理设备分配给虚拟机使用,这种方式须要硬件平台具有I/O透传技术,例如Intel VT-d技术。它能得到近乎本地的性能,而且CPU开销不高。操作系统
二、kvm和qemu的交互线程
Qemu建立虚拟机进入kvm:main函数经过调用kvm_init 和 machine->init来初始化kvm. 其中, machine->init会建立vcpu, 用一个线程去模拟vcpu, 该线程执行的函数为qemu_kvm_cpu_thread_fn, 而且该线程最终kvm_cpu_exec,该函数调用kvm_vcpu_ioctl切换到kvm中。code
Kvm运行并因io退出:在kvm中看到参数KVM_RUN,最后调用vcpu_enter_guest,而后 vmx_vcpu_run设置好寄存器状态以后调用VM_LAUNCH或者VM_RESUME进入guest vm。若是vm进行IO操做须要访问设备时,就会触发vm exit 返回到vmx_vcpu_run, vmx保存好vmcs而且记录下VM_EXIT_REASON后返回到调用该函数的vcpu_enter_guest, 在vcpu_enter_guest函数末尾调用了r = kvm_x86_ops->handle_exit(vcpu), 该函数对应于vmx_handle_exit函数, vmx_handle_exit 调用kvm_vmx_exit_handlers[exit_reason](vcpu),该语句根据exit_reason调用不一样的函数。io操做则是handle_io把数据填充到vcpu->run,就一路return到kvm_vcpu_ioctl,就ioctl返回到qemu的kvm_cpu_exec中。blog
从kvm返回到qemu后的处理:Qemu在kvm_cpu_exec中会看kvm_run的run->exit_reason若是是KVM_EXIT_IO就进入kvm_handle_io里处理。 当qemu完成IO操做后,会在kvm_cpu_exec函数的循环中,调用kvm_vcpu_ioctl从新进入kvm。进程
kvm_run,这是用于vcpu和应用层的程序(典型如qemu)通讯的一个结构,user space的 程序经过KVM__VCPU_MMAP_SIZE这个ioctl获得大小,而后映射到用户空间。
三、kvm的io处理流程
static int handle_io(struct kvm_vcpu *vcpu) { unsigned long exit_qualification; int size, in, string; unsigned port; exit_qualification = vmcs_readl(EXIT_QUALIFICATION); //获取exit qualification string = (exit_qualification & 16) != 0; //判断是否为string io (ins, outs) in = (exit_qualification & 8) != 0; //判断io方向,是in 仍是out ++vcpu->stat.io_exits; if (string || in) //若是是输入类的指令,或者是string io,就进入emulator处理 return emulate_instruction(vcpu, 0) == EMULATE_DONE; port = exit_qualification >> 16; //获得端口号 size = (exit_qualification & 7) + 1; //大小 skip_emulated_instruction(vcpu); //跳过这个指令 return kvm_fast_pio_out(vcpu, size, port); //进行out操做 }
Guest执行io指令 -> 发生vmexit-> 返回qemu -> 处理io
一、out指令虚拟:虚拟单个out指令,在KVM中能够直接把out的数据返回给qemu,qemu完成out操做。
流程:KVM的handle_io->kvm_fast_pio_out->emulator_pio_out_emulated后面是vcpu->arch.pio.count = 0函数中非string类型的 out操做能够一步完成,因此从qemu处理完返回kvm后不须要再进入emulator。在emulator_pio_out_emulated中,将IO数据memcpy到kvm和qemu共享buffer中,而后emulator_pio_in_out,将相应数据保存到kvm_run中就返回到qemu的kvm_cpu_exec的switch看run->exit_reason,若是是KVM_EXIT_IO则进入kvm_handle_io中和设备交互。
二、String或in指令虚拟:若是是in指令,qemu只能把获得的数据写到kvm_run中,kvm必须在下一次vmentry的时候,将qemu获得的数据放到相应的位置,因此,在handle_io中,若是是in或者string指令,没有调用skip_emulated_instruction,这样,在qemu完成in或者一次out以后,还会在一样的地方发生vmexit,这样再由emulator完成相应的处理,针对string类型的指令,emulator会进行解码等操做,确认io的次数和源操做数、目的操做数等。
流程:handle_io->emulate_instruction->x86_emulate_instruction对指令的decode,在过程当中会调用到em_in和em_out(这两个函数最后调用的emulator_pio_in_emulated中先经过和上面PIO同样的函数emulator_pio_in_out,正确返回代表qemu已经将模拟出的数据返回到参数val了,则可直接memcpy完成具体的将从qemu中获得的数据写到正确位置vcpu->arch.pio_data),设置若是是out,下次到KVM时直接进入emulator,若是是in,注册vcpu->arch.complete_userspace_io = complete_emulated_pio;须要在下次qemu进入kvm的时候,完成io,实际上就是将qemu获得的数据写到正确的位置。下次进入kvm,若是要完成in指令,会在函数kvm_arch_vcpu_ioctl_run中调用注册的complete_emulated_pio会再次调用emulate_instruction将数据写到正确位置(此次不用解码,而是直接em_in)。