QEMU內部:整體架構和線程模型


原文地址:http://blog.vmsplice.net/2011/03/qemu-internals-overall-architecture-and.html
全文翻譯如下:


1 概述

  這是講解QEMU內部原理的系列博客的第一篇,主要面向於開發者。旨在分享QEMU如何工作等相關的代碼基礎知識。
  運行一個客戶機涉及到執行客戶機代碼、處理計時器、處理I/O操作、相應模擬器的命令。併發處理這些工作要求一種安全調節資源的能力,當磁盤I/O或者模擬器命令需要執行很長時間的時候不暫停客戶機。現有兩個主流的架構可以用於響應來自多個源的事件:
  1. 並行架構:分離工作到可以並行的進程或者線程中執行。
  2. 事件驅動架構:通過執行一個主循環來發送事件到handler以對事件做處理。這一方法通常使用select(2)或者poll(2)系列的系統調用在多個文件描述符上進行等待。
  QEMU實際上使用了一種將事件驅動編程與線程相結合的混合架構。 這樣做是有道理的,因爲事件循環不能利用多個核心,因爲它只有一個執行線程。 此外,有時編寫專用線程來裝載一個特定任務而不是將其集成到事件驅動架構中更簡單。 然而,QEMU的核心是事件驅動的,大多數代碼在該環境中執行。

2 QEMU的事件驅動核心

  事件驅動的體系結構以事件循環爲中心,該事件循環將事件分派給處理函數。 QEMU的主事件循環是main_loop_wait(),它執行以下任務:
  1. 等待文件描述符變得可讀或可寫。 文件描述符起着至關重要的作用,因爲文件、套接字、管道、各種其他資源都是文件描述符。 可以使用qemu_set_fd_handler()添加文件描述符。
  2. 運行過期的計時器。 可以使用qemu_mod_timer()添加計時器。
  3. 運行下半部分(BHs),就像定時器立即到期一樣。 BH用於避免重入和溢出調用堆棧。 可以使用qemu_bh_schedule()添加BH。
  當文件描述符準備就緒、計時器到期、BH被調度時,事件循環將調用響應事件的回調。 回調有兩個關於其環境的簡單規則:
  1) 沒有其他核心代碼同時執行,因此不需要同步。 回調相對於其他核心代碼按順序和原子方式執行。 在任何給定時間只有一個控制線程執行核心代碼。
  2)不應執行阻塞系統調用或長時間運行的計算。 由於事件循環在繼續其他事件之前等待回調返回,因此避免在回調中花費無限量的時間是很重要的。 違反此規則會導致guest虛擬機暫停並且監視器無響應。
  第二條規則有時難以兌現,QEMU中有代碼阻塞。 事實上,甚至在qemu_aio_wait()中有一個嵌套的事件循環,它等待頂級事件循環處理的事件的子集。 希望將來通過重組代碼來消除這些違規行爲。 新代碼不可能有合法的理由去阻塞執行,一個解決方案是使用專用的worker threads來卸載長時間運行代碼或阻塞代碼。

3 卸載特殊的任務到worker threads

  儘管可以通過非阻塞方式執行許多I/O操作,但也存在沒有非阻塞效果的系統調用。 此外,有時長時間運行的計算只會佔用CPU並且難以分解爲回調。 在這些情況下,可以使用專用worker threads小心地將這些任務移出QEMU的核心線程。
  工作線程的一個示例用法是posix-aio-compat.c,一個異步文件I/O實現。 當核心QEMU發出aio請求時,它將被放置在隊列中。 worker threads將請求從隊列中取出並在核心QEMU之外執行它們。 它們可能會執行阻塞操作,因爲它們在自己的線程中執行,所以不會阻塞QEMU的其餘部分。 該實現負責在worker threads和核心QEMU之間執行必要的同步和通信。
  另一個例子是ui/vnc-jobs-async.c,它在worker threads中執行計算密集型鏡像壓縮和編碼。
  由於大多數核心QEMU代碼不是線程安全的,因此工作線程無法調用核心QEMU代碼。 像qemu_malloc()這樣的簡單實用程序是線程安全的,但這只是例外而不是規則。這給將工作線程事件傳遞迴核心QEMU帶來了問題。
  當worker thread需要通知核心QEMU時,一個管道或qemu_eventfd()文件描述符將添加到事件循環中。 worker thread可以寫入文件描述符,當文件描述符變得可讀時,事件循環將調用回調。 此外,必須使用信號來確保事件循環能夠在所有情況下運行。 這種方法由posix-aio-compat.c使用,在理解了客戶機代碼的執行方式後更有意義(尤其是信號的使用)。

4 執行客戶機代碼

  到目前爲止,我們主要研究了事件循環及其在QEMU中的核心作用。 同樣重要的是執行客戶機代碼的能力,沒有這種功能,QEMU可以響應事件但不會非常有用。
  執行訪客代碼有兩種機制:Tiny Code Generator(TCG)和KVM。 TCG使用動態二進制轉換(也稱爲即時(JIT)編譯)來模擬客戶機。 KVM利用現代Intel和AMD CPU中的硬件虛擬化擴展,直接在主機CPU上安全地執行訪客代碼。 出於本文的目的,先不關注各自具體技術細節,重要關注的是TCG和KVM都允許我們跳轉到客戶機代碼並執行它。
  跳轉到客戶機代碼會將宿主機的控制權交給客戶機。 當一個線程正在運行客戶機代碼時,它不能同時處於事件循環中,因爲客戶機具有(安全)CPU控制權。 通常,客戶機代碼中花費的時間是有限的,因爲對模擬設備寄存器的讀取和寫入以及其他異常導致我們離開客戶機並將控制權交還給QEMU。 在極端情況下,客戶機可以花費無限的時間而不放棄控制,這將使QEMU無法響應。
  爲了解決客戶機代碼佔用的問題,QEMU的控制信號線程用於跳出客戶機。 一個UNIX信號從當前的執行流程中轉移控制權並調用信號處理函數。 這允許QEMU採取措施來保留客戶機代碼並返回到QEMU主循環中,在主循環中事件循環可以有機會處理添加的各種事件。
  這樣做的結果是,如果QEMU當前在客戶機代碼中,則可能無法立即檢測到新事件。 大多數情況下,QEMU最終會處理事件,但這種額外的延遲本身就是一個性能問題。 因此,定時器、I/O完成、從worker threads到核心QEMU的通知都使用信號來確保事件循環立即運行。
  你可能想知道具有多個vcpus的事件循環與SMP客戶機之間的整體概況。 到此爲止已經講述了線程模型和客戶機代碼,我們可以討論整體架構。

5 iothread和non-iothread架構

  傳統架構是一個QEMU線程,它執行客戶機代碼和事件循環。 此模型也稱爲non-iothread或!CONFIG_IOTHREAD,並且是使用./configure && make構建QEMU時的默認模式。 QEMU線程執行客戶機代碼,直到異常或信號產生要求控制權。 然後它在select(2)中運行事件循環的一次迭代而不阻塞,之後它會重新回到客戶機代碼並重復以上過程,直到QEMU關閉。
  如果使用-smp 2以多個vcpus啓動客戶機機,則不會創建其他QEMU線程。 而是單個QEMU線程在兩個vcpus執行客戶機代碼和事件循環之間進行多路複用。 因此,non-iothread架構無法利用多核宿主機,並且可能導致SMP客戶機性能低下。
  請注意,儘管只有一個QEMU線程,但可能存在零個或多個worker threads。 這些線程可能是暫時的或永久的。 請記住,他們執行專門的任務,不執行客戶機代碼或處理事件。 我想強調這一點,因爲在模擬客戶機並將其解釋爲vcpu線程時,很容易被worker threads混淆。 請記住,non-iothread架構只有一個QEMU線程。
  較新的體系結構是每個vcpu一個QEMU線程加上一個專用的事件循環線程。 此模型稱爲iothread架構或CONFIG_IOTHREAD,可以在構建時使用./configure –enable-io-thread啓用。 iothread運行事件循環的同時,每個vcpu線程可以並行執行客戶機代碼,提供真正的SMP支持。 核心QEMU代碼永遠不會同時運行的規則通過全局互斥鎖來維護,該互斥鎖在vcpus和iothread之間同步核心QEMU代碼。 大多數情況下,vcpus將執行客戶機代碼,而不需要持有全局互斥鎖。 大多數情況下,iothread在select(2)中被阻塞,並且不需要持有全局互斥鎖。
  請注意,TCG不是線程安全的,因此即使在iothread模型下,它也會在單個QEMU線程中複用vcpus。 只有KVM可以利用per-vcpu線程。


備註:

  QEMU的主要線程:
  (1)主線程(main_loop),一個
  (2)vCPU線程,一個或者多個
  (3)I/O線程(aio),一個或者多個
  (4)worker thread(VNC/SPICE),一個


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章