![QEMU/KVM源码解析与应用](https://wfqqreader-1252317822.image.myqcloud.com/cover/969/40107969/b_40107969.jpg)
2.2 QEMU线程模型
2.2.1 QEMU线程模型简介
QEMU-KVM架构中,一个QEMU进程代表一个虚拟机。QEMU会有若干个线程,其中对于每个CPU会创建一个线程,还有其他的线程,如VNC线程、I/O线程、热迁移线程,QEMU线程模型如图2-12所示。
![](https://epubservercos.yuewen.com/81846C/20862584908972706/epubprivate/OEBPS/Images/33_02.jpg?sign=1739309245-WBUt4t9vtVCo4kr5MEQ8he7xpTEoGVoz-0-0c099175a590c813239485c3919a6528)
图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的过程。
![](https://epubservercos.yuewen.com/81846C/20862584908972706/epubprivate/OEBPS/Images/33_03.jpg?sign=1739309245-K10Jyc15BfgbFNCLz3V5uSWkpNdUlRMg-0-ba17b733076bd19bc39d3c269ced18d9)
![](https://epubservercos.yuewen.com/81846C/20862584908972706/epubprivate/OEBPS/Images/34_01.jpg?sign=1739309245-MZFv6iyrufCR4HVHq5ZbCkDn8FwkpJIr-0-5b8de0d1203f88b2a0c09ce9b3140ac3)
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,该函数的代码如下。
![](https://epubservercos.yuewen.com/81846C/20862584908972706/epubprivate/OEBPS/Images/34_02.jpg?sign=1739309245-I9d7ULiReET31Twc22b4MxBT3dMPn9Jz-0-9b2691c156c9a616e3cc409d0fdb727f)
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客户端进行交互。
![](https://epubservercos.yuewen.com/81846C/20862584908972706/epubprivate/OEBPS/Images/34_03.jpg?sign=1739309245-xni3LkTQLFSuClxMzZdEyOlCcrugQfIb-0-ddc24ce3f93a5dcaa86d6432c2fb381d)
3.I/O线程
设备模拟过程中可能会占用QEMU的大锁,所以如果是用磁盘类设备进行读写,会导致占用该锁较长时间。为了提高性能,会将这类操作单独放到一个线程中去。QEMU抽象出了一个新的类型TYPE_IOTHREAD,可以用来进行I/O线程的创建。比如virtio块设备在其对象实例化函数中添加了一个link属性,其对应的连接对象为一个TYPE_IOTHREAD。
![](https://epubservercos.yuewen.com/81846C/20862584908972706/epubprivate/OEBPS/Images/35_01.jpg?sign=1739309245-p9CD1ODaBUTb1Q96Uoebp8yuLabQTIcM-0-d4e2dbe1ce51fd972a914fd6174d2809)
当进行数据面的读写时,就可以使用这个iothread进行。
当然,QEMU还会有其他线程,比如说热迁移线程以及一些设备模拟自己创建的线程,这里就不一一介绍了。
如同Linux内核中的大锁,BQL会对QEMU虚拟机的性能造成很大影响。早期的QEMU代码在握有BQL时做的事情很多,QEMU多线程的主要动力是减少QEMU主线程的运行时间,QEMU在进行一些设备模拟的时候,VCPU线程会退出到QEMU,抢占QEMU大锁,如果这个时候有其他线程占据大锁,再做长时间的工作就会导致VCPU被挂起比较长的时间,所以将一些没有必要占据QEMU大锁的任务放到单独线程进行处理就能够增加VCPU的运行时间,这也是QEMU社区在多线程方向的努力方向,即尽量将任务从QEMU大锁中拿出来。