2.2 QEMU线程模型
2.2.1 QEMU线程模型简介
QEMU-KVM架构中,一个QEMU进程代表一个虚拟机。QEMU会有若干个线程,其中对于每个CPU会创建一个线程,还有其他的线程,如VNC线程、I/O线程、热迁移线程,QEMU线程模型如图2-12所示。
图2-12 QEMU线程模型
传统上,QEMU主事件循环所在的线程由于会不断监听各种I/O事件,所以被称为I/O线程。现在的I/O线程通常是指块设备层面的单独用来处理I/O事件的线程。每一个CPU都会有一个线程,通常叫作VCPU线程,其主要的执行函数是kvm_cpu_exec,比如图2-12中有3个VCPU线程。QEMU为了完成其他功能还会有一些辅助线程,如热迁移时候的migration线程、支持远程连接的VNC和SPICE线程等。
线程模型通常使用QEMU大锁进行同步,获取锁的函数为qemu_mutex_lock_iothread,解锁函数为qemu_mutex_unlock_iothread。实际上随着演变,现在这两个函数已经变成宏了。很多场合都需要BQL,比如os_host_main_loop_wait在有fd返回事件时,在进行事件处理之前需要调用qemu_mutex_lock_iothread获取BQL;VCPU线程在退出到QEMU进行一些处理的时候也会获取BQL。下面的代码是main函数主循环中获取BQL的过程。
2.2.2 QEMU线程介绍
1.VCPU线程
QEMU虚拟机的VCPU对应于宿主机上的一个线程,通常叫作VCPU线程。在x86_cpu_realizefn函数中进行CPU具现(CPU具现的概念会在2.4节中介绍)的时候会调用qemu_init_vcpu函数来创建VCPU线程。qemu_init_vcpu根据加速器的不同,会调用不同的函数来进行VCPU的创建,对于KVM加速器来说,这个函数是qemu_kvm_start_vcpu,该函数的代码如下。
qemu_thread_create调用了pthread_create来创建VCPU线程。VCPU线程用来执行虚拟机的代码,其线程函数是qemu_kvm_cpu_thread_fn。
2.VNC线程
在main函数中,会调用vnc_init_func对VNC模块进行初始化,经过vnc_display_init->vnc_start_worker_thread的调用最终创建VNC线程,VNC线程用来与VNC客户端进行交互。
3.I/O线程
设备模拟过程中可能会占用QEMU的大锁,所以如果是用磁盘类设备进行读写,会导致占用该锁较长时间。为了提高性能,会将这类操作单独放到一个线程中去。QEMU抽象出了一个新的类型TYPE_IOTHREAD,可以用来进行I/O线程的创建。比如virtio块设备在其对象实例化函数中添加了一个link属性,其对应的连接对象为一个TYPE_IOTHREAD。
当进行数据面的读写时,就可以使用这个iothread进行。
当然,QEMU还会有其他线程,比如说热迁移线程以及一些设备模拟自己创建的线程,这里就不一一介绍了。
如同Linux内核中的大锁,BQL会对QEMU虚拟机的性能造成很大影响。早期的QEMU代码在握有BQL时做的事情很多,QEMU多线程的主要动力是减少QEMU主线程的运行时间,QEMU在进行一些设备模拟的时候,VCPU线程会退出到QEMU,抢占QEMU大锁,如果这个时候有其他线程占据大锁,再做长时间的工作就会导致VCPU被挂起比较长的时间,所以将一些没有必要占据QEMU大锁的任务放到单独线程进行处理就能够增加VCPU的运行时间,这也是QEMU社区在多线程方向的努力方向,即尽量将任务从QEMU大锁中拿出来。