写点什么

QEMU 线程模型

  • 2023-04-09
    江苏
  • 本文字数:2522 字

    阅读完需:约 8 分钟

最近在阅读李强编著的《QEMU/KVM 源码解析与应用》这本书来学习 Linux 内核虚拟化相关知识,通过读书笔记的方式来提炼和归纳书中重要的知识点。本文主要内容是关于 QEMU 线程模型的介绍。


关注微信公众号:Linux 内核拾遗

文章来源:https://mp.weixin.qq.com/s/U8oLFKhbWyjnCnuR47KtOQ

1 QEMU 线程模型简介

QEMU-KVM 架构中,一个 QEMU 进程代表一个虚拟机。QEMU 会有若干个线程,其中对于每个 CPU 会创建一个线程,还有其他的线程,如 VNC 线程、I/O 线程、热迁移线程等。QEMU 线程模型如下图所示:



  • I/O 线程:传统上,I/O 线程指的是 QEMU 主事件循环所在的线程它会不断监听各种 I/O 事件;现在的 I/O 线程通常是指块设备层面的单独用来处理 I/O 事件的线程。

  • VCPU 线程:每一个 CPU 都会有一个线程,通常叫作 VCPU 线程,其主要的执行函数是 kvm_cpu_exec。

  • QEMU 为了完成其他功能还会有一些辅助线程,如热迁移时候的 migration 线程支持远程连接的 VNC 和 SPICE 线程等。

2 QEMU 大锁

这里补充一下原书中没有怎么详细介绍的 QEMU 大锁。


QEMU 在模拟 CPU 的时候会使用到一些锁来保证多线程的正确性。其中,QEMU 的大锁(big lock)是指 QEMU 中的一个全局锁,用于保护整个虚拟机的状态,防止多线程竞争。


QEMU 线程模型通常使用 QEMU 大锁进行同步,获取锁的函数为 qemu_mutex_lock_iothread,解锁函数为 qemu_mutex_unlock_iothread。实际上随着演变,现在这两个函数已经变成宏了。


很多场合都需要 BQL,比如:


  • os_host_main_loop_wait 在有 fd 返回事件时,在进行事件处理之前需要调用 qemu_mutex_lock_iothread 获取 BQL。

  • VCPU 线程在退出到 QEMU 进行一些处理的时候也会获取 BQL。


在 QEMU 中,由于大部分代码都被保护在大锁的范围内,这就导致了 QEMU 的多线程性能受到了一定的限制。因为只有一个线程可以获得大锁,其他线程必须等待该线程释放大锁才能继续执行,这样就会导致多线程并发执行时的效率降低。


为了提高 QEMU 的多线程性能,开发者们已经在不断努力地将代码分解为更小的锁颗粒度,以减少大锁的使用。同时,他们也在探索一些其他的优化技术,比如使用更高效的锁实现,以及使用无锁算法等。

3 QEMU 线程介绍

3.1 VCPU 线程

3.1.1 CPU 具现

介绍 VCPU 线程之前,这里先补充一下 CPU 具现的概念。


在 QEMU 中,CPU 具现(CPU realization)是指将虚拟 CPU 的架构实现为一个软件模拟器的过程。这个软件模拟器能够模拟真实硬件 CPU 的指令集、寄存器、缓存等特性,并且能够在不同的 CPU 架构之间进行转换,使得虚拟机能够运行在不同的 CPU 架构上。


QEMU 中的 CPU 具现是一个非常重要的概念,因为它决定了 QEMU 的虚拟化能力和性能。QEMU 支持多种 CPU 具现,包括 x86、ARM、MIPS、PowerPC 等。每种 CPU 具现都有其自己的实现方式和性能特征


QEMU 中的 CPU 具现还包括一些针对特定 CPU 架构的优化。例如,对于 x86 架构,QEMU 支持 KVM(Kernel-based Virtual Machine)加速,可以利用硬件虚拟化技术提高虚拟机的性能。对于 ARM 架构,QEMU 支持 TCG(Tiny Code Generator)加速,可以将虚拟指令动态翻译成宿主机的本地指令,提高虚拟机的执行效率


总之,QEMU 中的 CPU 具现是 QEMU 能够模拟各种 CPU 架构的基础,也是 QEMU 虚拟化能力和性能的关键所在。

3.1.2 VCPU 线程创建过程

QEMU 虚拟机的 VCPU 对应于宿主机上的一个线程,通常叫作 VCPU 线程。


在 x86_cpu_realizefn 函数中进行 CPU 具现的时候会调用 qemu_init_vcpu 函数来创建 VCPU 线程。qemu_init_vcpu 根据加速器的不同,会调用不同的函数来进行 VCPU 的创建,对于 KVM 加速器来说,这个函数是 qemu_kvm_start_vcpu:


// cpus.cstatic void qemu_kvm_start_vcpu(CPUState* cpu) {  char thread_name[VCPU_THREAD_NAME_SIZE];  ...  qemu_thread_create(cpu->thread, thread_name, qemu_kvm_cpu_thread_fn,                     cpu, QEMU_THREAD_JOINABLE);}
复制代码


qemu_thread_create 调用了 pthread_create 来创建 VCPU 线程。VCPU 线程用来执行虚拟机的代码,其线程函数是 qemu_kvm_cpu_thread_fn。

3.2 VNC 线程

在 main 函数中,会调用 vnc_init_func 对 VNC 模块进行初始化,经过 vnc_display_init->vnc_start_worker_thread 的调用最终创建 VNC 线程,VNC 线程用来与 VNC 客户端进行交互。


// ui/vnc-jobs.cvoid vnc_start_worker_thread(void) {  VncJobQueue* q;  ...  q = vnc_queue_init();  qemu_thread_create(&q->thread, "vnc_worker", vnc_worker_thread_fn, q                    QEMU_THREAD_DETACHED);  queue = q; /* Set global queue */}
复制代码

3.3 I/O 线程

设备模拟过程中可能会占用 QEMU 的大锁,所以如果是用磁盘类设备进行读写,会导致占用该锁较长时间。为了提高性能,会将这类操作单独放到一个线程中去。


QEMU 抽象出了一个新的类型 TYPE_IOTHREAD,可以用来进行 I/O 线程的创建。比如 virtio 块设备在其对象实例化函数中添加了一个 link 属性,其对应的连接对象为一个 TYPE_IOTHREAD。


// hw/block/virtio-blk.cstatic void virtio_blk_instance_init(Object* obj) {  VirtIOBlock* s = VIRTIO_BLK(obj);    object_property_add_link(obj, "iothread", TYPE_IOTHREAD,                          (OBject**)&s->conf.iothread,                          qdev_prop_allow_set_link_before_realize,                          OBJ_PROP_LINK_UNREF_ON_RELEASE, NULL);  device_add_bootindex_property(obj, &s->conf.conf.bootindex,                               "bootindex", "/disk@0,0",                               DEVICE(obj), NULL);}
复制代码


当进行数据面的读写时,就可以使用这个 iothread 进行。

4 总结

如同 Linux 内核中的大锁,BQL 会对 QEMU 虚拟机的性能造成很大影响。


早期的 QEMU 代码在握有 BQL 时做的事情很多,QEMU 多线程的主要动力是减少 QEMU 主线程的运行时间,QEMU 在进行一些设备模拟的时候,VCPU 线程会退出到 QEMU,抢占 QEMU 大锁,如果这个时候有其他线程占据大锁,再做长时间的工作就会导致 VCPU 被长时间挂起,所以将一些没有必要占据 QEMU 大锁的任务放到单独线程进行处理就能够增加 VCPU 的运行时间,这也是 QEMU 社区在多线程方向的努力方向,即尽量将任务从 QEMU 大锁中拿出来。

参考文献

  1. QEMU/KVM 源码解析与应用 - 李强


关注微信公众号:Linux 内核拾遗

文章来源:https://mp.weixin.qq.com/s/U8oLFKhbWyjnCnuR47KtOQ


发布于: 刚刚阅读数: 5
用户头像

聚沙成塔 2023-01-12 加入

分享Linux内核开发相关的编程语言、开发调试工具链、计算机组成及操作系统内核知识、Linux社区最新资讯等

评论

发布
暂无评论
QEMU线程模型_线程模型_Linux内核拾遗_InfoQ写作社区