qemu源碼架構

前言:本文主要歸納了QEMU的代碼結構,特別從代碼翻譯的角度分析了QEMU是如何將客戶機代碼翻譯成TCG代碼和主機代碼而且最終執行的過程。而且在最後描述了QEMU和KVM之間聯繫的紐帶。html

申明:本文前面部分從qemu detailed study第七章翻譯而來。前端

 

1.代碼結構

如咱們所知,QEMU是一個模擬器,它可以動態模擬特定架構的CPU指令,如X86,PPC,ARM等等。QEMU模擬的架構叫目標架構,運行 QEMU的系統架構叫主機架構,QEMU中有一個模塊叫作微型代碼生成器(TCG),它用來將目標代碼翻譯成主機代碼。以下圖所示。算法

    


 

咱們也能夠將運行在虛擬cpu上的代碼叫作客戶機代碼,QEMU的主要功能就是不斷提取客戶機代碼而且轉化成主機指定架構的代碼。整個翻譯任務分爲兩個部分:第一個部分是將作目標代碼(TB)轉化成TCG中間代碼,而後再將中間代碼轉化成主機代碼。 後端

QEMU的代碼結構很是清晰可是內容很是複雜,這裏先簡單分析一下整體的結構 架構

1. 開始執行:

主要比較重要的c文件有:/vl.c,/cpus.c, /exec-all.c, /exec.c, /cpu-exec.c. 函數

QEMU的main函數定義在/vl.c中,它也是執行的起點,這個函數的功能主要是創建一個虛擬的硬件環境。它經過參數的解析,將初始化內存,須要的模擬的設備初始化,CPU參數,初始化KVM等等。接着程序就跳轉到其餘的執行分支文件如:/cpus.c, /exec-all.c, /exec.c, /cpu-exec.c. oop

2. 硬件模擬

全部的硬件設備都在/hw/ 目錄下面,全部的設備都有獨自的文件,包括總線,串口,網卡,鼠標等等。它們經過設備模塊串在一塊兒,在vl.c中的machine _init中初始化。這裏就不講每種設備是怎麼實現的了。 ui

3.目標機器

如今QEMU模擬的CPU架構有:Alpha, ARM, Cris, i386, M68K, PPC, Sparc, Mips, MicroBlaze, S390X and SH4.spa

咱們在QEMU中使用./configure 能夠配置運行的架構,這個腳本會自動讀取本機真實機器的CPU架構,而且編譯的時候就編譯對應架構的代碼。對於不一樣的QEMU作的事情都不一樣,因此不一樣架 構下的代碼在不一樣的目錄下面。/target-arch/目錄就對應了相應架構的代碼,如/target-i386/就對應了x86系列的代碼部分。雖然 不一樣架構作法不一樣,可是都是爲了實現將對應客戶機CPU架構的TBs轉化成TCG的中間代碼。這個就是TCG的前半部分。 .net

4.主機

這個部分就是使用TCG代碼生成主機的代碼,這部分代碼在/tcg/裏面,在這個目錄裏面也對應了不一樣的架構,分別在不一樣的子目錄裏面,如i386就在/tcg/i386中。整個生成主機代碼的過程也能夠教TCG的後半部分。

5.文件總結和補充:

/vl.c:                                     最主要的模擬循環,虛擬機機器環境初始化,和CPU的執行。

/target-arch/translate.c    將客戶機代碼轉化成不一樣架構的TCG操做碼。

/tcg/tcg.c                              主要的TCG代碼。

/tcg/arch/tcg-target.c         將TCG代碼轉化生成主機代碼

/cpu-exec.c                          其中的cpu-exec()函數主要尋找下一個TB(翻譯代碼塊),若是沒找到就請求獲得下一個TB,而且操做生成的代碼塊。

2. TCG - 動態翻譯

QEMU在 0.9.1版本以前使用DynGen翻譯c代碼.當咱們須要的時候TCG會動態的轉變代碼,這個想法的目的是用更多的時間去執行咱們生成的代碼。當新的代 碼從TB中生成之後, 將會被保存到一個cache中,由於不少相同的TB會被反覆的進行操做,因此這樣相似於內存的cache,可以提升使用效率。而 cache的刷新使用LRU算法。

 

 

編譯器在執行器會從源代碼中產生目標代碼,像GCC這種編譯器,它爲了產生像函數調用目標代碼會產生一些特殊的彙編目標代碼,他們可以讓編譯器須要知道在調用函數。須要什麼,以及函數調用之後須要返回什麼,這些特殊的彙編代碼產生過程就叫作函數的Prologue和Epilogue,這裏就叫前端和後段吧。我在其餘文章中也分析過彙編調用函數的過程,至於彙編裏面函數調用過程當中寄存器是如何變化的,在本文中就再也不描述了。

函數的後端會恢復前端的狀態,主要作下面2點:

1. 恢復堆棧的指針,包括棧頂和基地址。

2. 修改cs和ip,程序回到以前的前端記錄點。

TCG就如編譯器同樣能夠產生目標代碼,代碼會保存在緩衝區中,當進入前端和後端的時候就會將TCG生成的緩衝代碼插入到目標代碼中。

接下來咱們就來看下如何翻譯代碼的:

客戶機代碼

 

TCG中間代碼


主機代碼

 

 

3. TB鏈

在QEMU中,從代碼cache到靜態代碼再回到代碼cache,這個過程比較耗時,因此在QEMU中涉及了一個TB鏈將全部TB連在一塊兒,可讓一個TB執行完之後直接跳到下一個TB,而不用每次都返回到靜態代碼部分。具體過程以下圖:

 

4. QEMU的TCG代碼分析

接下來來看看QEMU代碼中中到底怎麼來執行這個TCG的,看看它是如何生成主機代碼的。

main_loop(...){/vl.c} : 

函數main_loop 初始化qemu_main_loop_start()而後進入無限循環cpu_exec_all() , 這個是QEMU的一個主要循環,在裏面會不斷的判斷一些條件,如虛擬機的關機斷電之類的。

qemu_main_loop_start(...){/cpus.c} :

函數設置系統變量 qemu_system_ready = 1而且重啓全部的線程而且等待一個條件變量。 

cpu_exec_all(...){/cpus.c} :

它是cpu循環,QEMU可以啓動256個cpu核,可是這些核將會分時運行,而後執行qemu_cpu_exec() 。

struct CPUState{/target-xyz/cpu.h} :

它是CPU狀態結構體,關於cpu的各類狀態,不一樣架構下面還有不一樣。

 

cpu_exec(...){/cpu-exec.c}:

這個函數是主要的執行循環,這裏第一次翻譯以前說道德TB,TB被初始化爲(TranslationBlock *tb) ,而後不停的執行異常處理。其中嵌套了兩個無限循環 find tb_find_fast() 和tcg_qemu_tb_exec().

cantb_find_fast()爲客戶機初始化查詢下一個TB,而且生成主機代碼。

tcg_qemu_tb_exec()執行生成的主機代碼 

struct TranslationBlock {/exec-all.h}:

結構體TranslationBlock包含下面的成員:PC, CS_BASE, Flags (代表TB), tc_ptr (指向這個TB翻譯代碼的指針), tb_next_offset[2], tb_jmp_offset[2] (接下去的Tb), *jmp_next[2], *jmp_first (以前的TB).

 

tb_find_fast(...){/cpu-exec.c} :

函數經過調用得到程序指針計數器,而後傳到一個哈希函數從 tb_jmp_cache[] (一個哈希表)獲得TB的因此,因此使用tb_jmp_cache能夠找到下一個TB。若是沒有找到下一個TB,則使用tb_find_slow。

 tb_find_slow(...){/cpu-exec.c}:

這個是在快速查找失敗之後試圖去訪問物理內存,尋找TB。

tb_gen_code(...){/exec.c}:

開始分配一個新的TB,TB的PC是剛剛從CPUstate裏面經過using get_page_addr_code()找到的

phys_pc = get_page_addr_code(env, pc);

tb = tb_alloc(pc);

ph當調用cpu_gen_code() 之後,接着會調用tb_link_page()它將增長一個新的TB,而且指向它的物理頁表。

cpu_gen_code(...){translate-all.c}:

函數初始化真正的代碼生成,在這個函數裏面有下面的函數調用:

gen_intermediate_code(){/target-arch/translate.c}->gen_intermediate_code_internal(){/target-arch/translate.c }->disas_insn(){/target-arch/translate.c}

disas_insn(){/target-arch/translate.c}

函數disas_insn() 真正的實現將客戶機代碼翻譯成TCG代碼,它經過一長串的switch case,將不一樣的指令作不一樣的翻譯,最後調用tcg_gen_code。

 

tcg_gen_code(...){/tcg/tcg.c}:

這個函數將TCG的代碼轉化成主機代碼,這個就不細細說明了,和前面相似。

 

#define tcg_qemu_tb_exec(...){/tcg/tcg.g}:

經過上面的步驟,當TB生成之後就經過這個函數進行執行.

next_tb = tcg_qemu_tb_exec(tc_ptr) :

extern uint8_t code_gen_prologue[];

#define tcg_qemu_tb_exec(tb_ptr) ((long REGPARM(*)(void *)) code_gen_prologue)(tb_ptr)

 

經過上面的步驟咱們就解析了QEMU是如何將客戶機代碼翻譯成主機代碼的,瞭解了TCG的工做原理。接下來看看QEMU與KVM是怎麼聯繫的。

5. QEMU中的IOCTL

在QEMU-KVM中,用戶空間的QEMU是經過IOCTL與內核空間的KVM模塊進行通信的。

1. 建立KVM

在/vl.c中經過kvm_init()將會建立各類KVM的結構體變量,而且經過IOCTL與已經初始化好的KVM模塊進行通信,建立虛擬機。而後建立VCPU,等等。

2. KVM_RUN

這個IOCTL是使用最頻繁的,整個KVM運行就不停在執行這個IOCTL,當KVM須要QEMU處理一些指令和IO等等的時候就會退出經過這個IOCTL退回到QEMU進行處理,否則就會一直在KVM中執行。

它的初始化過程:

vl.c中調用machine->init初始化硬件設備接着調用pc_init_pci,而後再調用pc_init1。

接着經過下面的調用初始化KVM的主循環,以及CPU循環。在CPU循環的過程當中不斷的執行KVM_RUN與KVM進行交互。

pc_init1->pc_cpus_init->pc_new_cpu->cpu_x86_init->qemu_init_vcpu->kvm_init_vcpu->ap_main_loop->kvm_main_loop_cpu->kvm_cpu_exec->kvm_run

3.KVM_IRQ_LINE

這個IOCTL和KVM_RUN是不一樣步的,它也是個頻率很是高的調用,它就是通常中斷設備的中斷注入入口。當設備有中斷就經過這個IOCTL最終 調用KVM裏面的kvm_set_irq將中斷注入到虛擬的中斷控制器。在kvm中會進一步判斷屬於什麼中斷類型,而後在合適的時機寫入vmcs。固然在 KVM_RUN中會不斷的同步虛擬中斷控制器,來獲取須要注入的中斷,這些中斷包括QEMU和KVM自己的,並在從新進入客戶機以前注入中斷。

 

總結: 經過這篇文章可以大概的瞭解QEMU的代碼結構,其中主要包括TCG翻譯代碼的過程以及QEMU和KVM的交互過程。

http://blog.chinaunix.net/uid-26941022-id-3510672.html

相關文章
相關標籤/搜索