代碼分析文章《KVM虛擬機代碼揭祕——QEMU代碼結構分析》、《KVM虛擬機代碼揭祕——中斷虛擬化》、《KVM虛擬機代碼揭祕——設備IO虛擬化》、《KVM虛擬機代碼揭祕——QEMU的PCI總線與設備(上)》、《KVM虛擬機代碼揭祕——QEMU的PCI總線與設備(下)》。先從大的方面分析代碼結構,而後分中斷、IO、PCI總線與設備詳細介紹。前端
關於TCG的解釋:TCG(Tiny Code Generator),QEMU的官方解釋在http://wiki.qemu-project.org/Documentation/TCG。linux
TCG的做用就是將Target的指令經過TCG前端轉換成TCG ops,進而經過TCG後端轉換成Host上運行的指令。ios
須要將QEMU移植到一個新CPU上運行,須要關注TCG後端。須要基於QEMU模擬一個新CPU,須要關注TCG前端。算法
在根目錄生成,參照Makefile可知有以下文件組成:後端
qemu-img$(EXESUF): qemu-img.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS) |
因爲target比較多,編譯也費時。能夠指定便以特定的target:架構
./configure --target-list=x86_64-softmmu |
qemu-system-x86_64的入口定義在vl.c的main中:async
main ->main_loop ->main_loop_wait ->os_host_main_loop_wait |
QEMU的main函數定義在vl.c中,是執行程序的起點,主要功能是創建一個虛擬的硬件環境。函數
. |
QEMU是一個模擬器,它可以動態模擬特定架構CPU指令,QEMU模擬的架構叫目標架構;運行QEMU的系統架構叫主機架構。
QEMU中有一個模塊叫微型代碼生成器,將目標代碼翻譯成主機代碼。
運行在虛擬CPU上的代碼叫作客戶機代碼,QEMU主要功能就是不斷提取客戶機代碼而且轉化成主機代碼。
整個翻譯分紅兩部分:將目標代碼(TB)轉化成TCG中間代碼,而後再將中間代碼轉化成主機代碼。
當新的代碼從TB(Translation Block)中生成之後,將會保存到一個cache中,由於不少相同的TB會被反覆的進行操做,因此這樣相似於內存的cache,可以提升使用效率。而cache的刷新使用LRU算法。
tb_gen_code ->gen_intermediate_code ->tcg_gen_code |
由tb_gen_code調用,將客戶機代碼轉換成主機代碼。gen_intermediate_code以前是客戶及代碼,tcg_gen_code以後是主機代碼,二者之間是TCG中間代碼。
x86_cpu_realizefn 架構相關初始化函數 ->qemu_init_vcpu ->qemu_tcg_init_vcpu ->qemu_tcg_cpu_thread_fn vcpu線程函數 ->tcg_cpu_exec ->cpu_exec 這個函數是主要的執行循環,這裏第一次翻譯TB,而後不停的執行異常處理。 ->tb_find 首先在Hash表中查找,若是找不到則調用tb_gen_code建立一個TB。 ->tb_gen_code 分配一個新的TB。 ->gen_intermediate_code ->tcg_gen_code 將TCG代碼轉換成主機代碼。 ->cpu_loop_exec_tb ->cpu_tb_exec 執行TB主機代碼 ->tcg_qemu_tb_exec |
KVM中斷虛擬化主要依賴於VT-x技術,VT-x主要提供了兩種中斷事件機制,分別是中斷退出和中斷注入。
中斷退出:指虛擬機發生中斷時,主動式的客戶機發生VM-Exit,這樣可以在主機中實現對客戶機中斷的注入。
中斷注入:是指將中斷寫入VMCS對應的中斷信息位,來實現中斷的注入,當中斷完成後經過讀取中斷的返回信息來分析中斷是否正確。
中斷注入的標誌性函數kvm_set_irq,是中斷注入的最開始。
第一個參數s,傳遞設置IRQ須要的vmfd句柄,以及IRQ的ioctl類型。
第2、三參數,是IRQ中斷號,以及觸發類型。
int kvm_set_irq(KVMState *s, int irq, int level) assert(kvm_async_interrupts_enabled()); event.level = level; return (s->irq_set_ioctl == KVM_IRQ_LINE) ? 1 : event.status; |
上面的ioctl對應內核中的kvm_vm_ioctl,內核首先case到KVM_IRQ_LINE。而後解析
static long kvm_vm_ioctl(struct file *filp, r = -EFAULT; r = kvm_vm_ioctl_irq_line(kvm, &irq_event, r = -EFAULT; r = 0; } |
KVM中斷路由(何爲?)
kvm_arch_vm_ioctl ->KVM_CREATE_IRQCHIP(kvm_setup_default_irq_routing) ->kvm_set_irq_routing ->setup_routing_entry ->kvm_set_routing_entry ->KVM_IRQCHIP_PIC_MASTER(kvm_set_pic_irq) ->KVM_IRQCHIP_PIC_SLAVE(kvm_set_pic_irq) ->KVM_IRQCHIP_IOAPIC(kvm_set_ioapic_irq) ->KVM_IRQ_ROUTING_MSI(kvm_set_msi) ->KVM_IRQ_ROUTING_HV_SINT(kvm_hv_set_sint) |
從上能夠看出針對不一樣類型的ROUTING方式和IRQCHIP,跳轉到對應的中斷注入函數。IRQCHIP類型的中斷路由有PIC和IOAPIC;還有MSI和SINT類型。
PIC全稱 Programmable Interrupt Controller,一般是指Intel 8259A雙片級聯構成的最多支持15個interrupts的中斷控制系統。
APIC全稱Advanced Programmable Interrupt Controller,APIC是爲了多核平臺而設計的。它由兩個部分組成IOAPIC和LAPIC,其中IOAPIC一般位於南橋中用於處理橋上的設備所產生的各類中斷,LAPIC則是每一個CPU都會有一個。IOAPIC經過APICBUS(如今都是經過FSB/QPI)將中斷信息分 派給每顆CPU的LAPIC,CPU上的LAPIC可以智能的決定是否接受系統總線上傳遞過來的中斷信息,並且它還能夠處理Local端中斷的 pending、nesting、masking,以及IOAPIC於Local CPU的交互處理。
設置好虛擬中斷控制器以後,在KVM_RUN退出之後,就開始遍歷虛擬中斷控制器,若是發現中斷,就將中斷寫入中斷信息位.
vcpu_run ->vcpu_enter_guest ->inject_pending_event |
inject_pending_event在進入Guest以前被調用。
KVM虛擬機設備模擬實在QEMU中實現的,而KVM實現的實質上只是IO的攔截。真正的虛擬設備IO地址註冊實在QEMU代碼裏面實現的。
QEMU中,初始化硬件設備的時候須要註冊IO空間,有兩種方法:
PS:發覺這裏面介紹的代碼和最新的4.x已經很大差別,因此略過。
QEMU在初始化硬件的時候,最開始的函數就是pc_init1。在這個函數裏面會相繼的初始化CPU、中斷控制器、ISA總線,而後就要判斷是否須要支持PCI。若是支持則調用i440fx_init初始化PCI總線。
static void pc_init1(MachineState *machine, |
i440fx_init函數主要參數就是以前初始化好的ISA總線以及中斷控制器,返回值就是PCI總線,以後咱們就能夠將設備通通掛載在這個上面。
在QEMU中,全部的設備包括總線,橋,通常設備都對應一個設備結構,經過register函數將全部的設備連接起來,就像Linux的模塊同樣,在QEMU啓動的時候會初始化全部的QEMU設備,而對於PCI設備來講,QEMU在初始化之後還會進行一次RESET,將全部的PCI bar上的地址清空,而後進行統一分配。
QEMU(x86)裏面的PCI的默認PCI設都是掛載主總線上的,貌似沒有看到PCI-PCI橋,而橋的做用通常也就是鏈接兩個總線,而後進行終端和IO的映射。
通常的PCI設備其實和橋很像,甚至更簡單,關鍵區分橋和通常設備的地方就是class屬性和bar地址。
struct PCIDevice表示了PCI設備的信息。
pci_register_bat主要給bar分配IO地址。
void pci_register_bar(PCIDevice *pci_dev, int region_num, assert(region_num >= 0); r = &pci_dev->io_regions[region_num]; wmask = ~(size - 1); addr = pci_bar(pci_dev, region_num); if (!(r->type & PCI_BASE_ADDRESS_SPACE_IO) && |
代碼對不上,略過。