虛擬化:
KVM是一個基於Linux內核的虛擬機,屬於完全虛擬化。虛擬機監控的實現模型有兩類:監控模型(Hypervisor)和宿主機模型(Host-based)。由於監控模型需要進行處理器調度,還需要實現各種驅動程序,以支撐運行其上的虛擬機,因此實現難度上一般要大於宿主機模型。KVM的實現采用宿主機模型(Host-based),KVM是集成在Linux內核中的,因此可以自然地使用Linux內核提供的內存管理、多處理器支持等功能,易於實現,而且還可以隨着Linux內核的發展而發展。另外,目前KVM的所有I/O虛擬化工作是借助Qemu完成的. 詳見(http://blog.csdn.net/yearn520/article/details/6461047#comments)
Libvirt 庫是一種實現 Linux 虛擬化功能的 Linux API, libvirt 本身構建於一種抽象的概念之上。它為受支持的虛擬機監控程序實現的常用功能提供通用的 API。

libvirt 將物理主機稱作節點,將來賓操作系統稱作域。這里需要注意的是,libvirt(及其應用程序)在宿主 Linux 操作系統(域 0)中運行。

QEMU概述:
目前,qemu支持兩種操作模式:
- 全系統仿真模式,此時qemu就相當於一台完整的pc機,用來運行不同的操作系統或調試操作系統的代碼。
- 用戶態仿真模式。在這種模式下,其他平台的程序(x86平台上運行為arm平台編譯的程序),能夠進行方便的交叉編譯和調試,比如wine。
眾所周知,Bochs 是一款可移植的IA-32仿真器,它利用模擬的技術來仿真目標系統,具體來說,將是將目標系統的指令分解,然后模擬分解后的指令以達到同樣的效果。這種方法將每一條目標指令分解成多條主機系統的指令,很明顯會大大降低仿真的速度。
qemu則是采用動態翻譯的技術,先將目標代碼翻譯成一系列等價的被稱為“微操作”(micro-operations)的指令,這樣做的好處就是,代碼是是按塊翻譯,按塊執行的,不像Bochs翻譯一條指令,馬上就執行一條指令。將guest binary instructions動態翻譯成host binary instructions,之后由host運行翻譯后的指令。在qemu-0.9之前的版本都采用dyngen的動態翻譯技術,而從qemu-0.10開始的版本開始采用TCG(Tiny Code Generator)的翻譯技術。
采用dyngen 動態翻譯技術的資料主要有以下兩篇文章,是了解動態翻譯技術入門的好文章(在后續的分析中,會簡單介紹dyngen技術):
- QEMU, a Fast and Portable Dynamic Translator
-
Porting QEMU to Plan 9: QEMU Internals and Port Strategy
TCG動態翻譯技術的幾個概念:
與dyngen一樣,TCG的“function”與qemu的TBs(Translated Block)相對應,即以分支跳轉指令結束的代碼段。
TCG是qemu的核心,主要實現了以下翻譯流程:
guest binary instructions -> TCG IR -> host binary instructions TCG 定義了一組IR(intermediate representation),這些IR大致可以分為以下幾類:
- Mov類操作: mov, movi, ...
TCG 動態翻譯過程:
TCG中間代碼

主機代碼

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

QEMU安裝配置:
創建虛擬磁盤:
qemu-img create -f qcow2 -o preallocation=metadata windows.img 3G
安裝系統,使用光驅:
qemu -hda windows.img -cdrom /dev/cdrom -boot d
讀iso文件:
qemu winxp.img -cdrom deepXP.iso -boot d
啟動虛擬機
$ qemu winxp.img -m 1024 -smp 2 -soundhw es1370 -vga std -boot c
其中,-m 1024指分配1G內存,-smp 2指分配兩個CPU,-soundhw es1370指加載此類型聲卡設備,-vga std指加載此顯卡設備。
使用主機上的usb2.0設備, 解決方式是在qemu的啟動選項中加上:
-device usb-ehci,id=ehci
啟動后通過control-alt-1進入控制台,注意把qemu的控制台重定向到stdin,在啟動選項中加入:
-monitor stdio
在qemu控制台查看主機的usb設備:
info usbhost
找到你想要的usb設備。所有usb設備都將列出Bus, Addr, Port和Speed。如果速度在480 Mb/s,那么就需要使用usb2.0連接。假設Bus和Port分別是2和1.6。通過往下命令添加入虛擬機:
device_add usb-host,bus=ehci.0,hostbus=2,hostport=1.6
在虛擬機中添加相應驅動就可以工作了。
用-net nic為虛擬機創建虛擬機網卡。例如,qemu的命令行選項
-net nic,model=pcnet
再查看 #lspci | grep Eth
02:00.0 Ethernet controller: Advanced Micro Devices [AMD] 79c970 [PCnet32 LANCE] (rev 10)
虛擬機的網絡設備連接在qemu虛擬的VLAN中。每個qemu的運行實例是宿主機中的一個進程,而每個這樣的進程中可以虛擬一些VLAN,虛擬機網絡設備接入這些VLAN中。當某個VLAN上連接的網絡設備發送數據幀,與它在同一個VLAN中的其它網路設備都能接收到數據幀。上面的例子中對虛擬機的pcnet網卡沒有指定其連接的VLAN號,那么qemu默認會將該網卡連入vlan0。
下面這個例子更具一般性:
-net nic,model=pcnet -net nic,model=rtl8139,vlan=1, -net nic,model=ne2k_pci,vlan=1
該命令為虛擬機創建了三塊網卡,其中第一塊網卡類型是pcnet,連入vlan0;第二塊網卡類型是 rtl8139,第三塊網卡類型是ne2k_pci,這兩塊都連入vlan1,所以第二塊網卡與第三塊網卡可以互相通信,但它們與第一塊網卡不能直接通信。
單網卡用戶模式:
# qemu-system-x86_64 -m 128 winxp.img -net nic,vlan=0,macaddr=52:54:00:12:342,model=pcnet,addr=08 –net user
---恢復內容開始---
wget http://ftp.nl.debian.org/debian/dists/squeeze/main/installer-armel/20110106+squeeze4+b3/images/versatile/netboot/initrd.gz
wget http://ftp.nl.debian.org/debian/dists/squeeze/main/installer-armel/20110106+squeeze4+b3/images/versatile/netboot/vmlinuz-2.6.32-5-versatile
參考 http://www.cnblogs.com/Akann/archive/2012/01/01/2307804.html
下載下面3個文件
arm嵌入式linux的內核(1.3M0:
http://free-linux.org/upload/arm/vmlinuz-2.6.26-2-versatile
arm嵌入式linux的啟動initrd文件(2.1M):
http://free-linux.org/upload/arm/initrd.img-2.6.26-2-versatile
arm嵌入式linux磁盤鏡像(368M):
http://free-linux.org/upload/arm/hda.img.bz2(需要解壓縮后使用)
3.使用下面的命令引導linux內核和磁盤鏡像
linux中執行:
qemu-system-arm -M versatilepb -kernel vmlinuz-2.6.26-2-versatile -hda hda.img -initrd
initrd.img-2.6.26-2-versatile -append "root=/dev/sda1" -m 256
或者下載bash腳本來運行:
http://free-linux.org/upload/arm/arm-linux.sh.gz
(解壓縮后需要用chmod +x arm-linux.sh改成可執行的)
windows用戶需要把三個文件放到qemu的軟件目錄中,將qemu-arm.bat的內容換成
qemu-system-arm.exe -M versatilepb -kernel vmlinuz-2.6.26-2-versatile -hda hda.img -initrd
initrd.img-2.6.26-2-versatile -append "root=/dev/sda1" -m 256
或者下載bat腳本運行:
http://free-linux.org/upload/arm/arm-linux.bat
4.登錄
啟動后看到登錄提示符,用戶名輸入root,密碼debian。若遇到磁盤無法自檢通過可以重新啟動一下試試。
---恢復內容結束---
Convert from VMware to QEMU
This is a quick and dirty quide to converting an Vmware disk file (.vmdk) to something usable by QEMU.
Now QEMU cannot use the vmdk file directly, but has a facility in qemu-img to perform the conversion.
qemu-img convert win2kpro.vmdk -O qcow win2kpro.imgThe next step is totally optional but allows to build a snapshot overlay to which all further rights will perform.
qemu-img create -b win2kpro.img -f qcow win2kpro.ovl Formating 'win2kpro.ovl', fmt=qcow, backing_file=win2kpro.img, size=8388608 kBNow win2kpro.ovl should be usable by qemu. I fire it up and am greeting with promising pictures of Windows 2000 booting up, but after about 10 seconds a BSOD which says INACCESSIBLE_BOOT_DEVICE
I tried this with a Linux instance (centos-3) and it works :)After conversion and boot up, kudzu runs and handles the (virtual) hardware differences. Also the sda references in /etc/fstab are now hda, so some massaging of fstab is necessary.
QEMU 源碼分析 :
總結和補充:
/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,並且操作生成的代碼塊。
Target指令 ----> TCG ----> Host指令
TCG成為QEMU新的翻譯引擎,全稱為“Tiny Code Generator”,和一個真正的編譯器后端一樣,主要負責分析、優化Target代碼以及生成Host代碼。
tcg/tcg.c : static inline void tcg_out8(TCGContext *s, uint8_t v) { ... }
tcg/i386/tcg-target.c : static inline void tcg_out_movi(TCGContext *s, TCGType type, int ret, int32_t arg) { if (arg == 0) { /* xor r0,r0 */ tcg_out_modrm(s, 0x01 | (ARITH_XOR << 3), ret, ret); } else { tcg_out8(s, 0xb8 + ret); // 輸出操作碼,ret是寄存器索引 tcg_out32(s, arg); // 輸出操作數 } }
0xb8 - 0xbf 正是x86指令中的 mov R, Iv 系列操作的16進制碼,所以,tcg_out_movi 的功能就是輸出 mov 操作的指令碼到緩沖區中。
可以看出,TCG在生成目標指令的過程中是采用硬編碼的,因此,要讓TCG運行在不同的Host平台上,就必須為不同的平台編寫微指令函數。
以一條Target指令 jmp f000:e05b 來講述它是如何被翻譯成Host指令的。其中幾個關鍵變量的定義如下:
gen_opc_buf:操作碼緩沖區
gen_opparam_buf:參數緩沖區
gen_code_buf:存放翻譯后指令的緩沖區
gen_opc_ptr、gen_opparam_ptr、gen_code_ptr三個指針變量分別指向上述緩沖區。
jmp f000:e05b 的編碼是:EA 5B E0 00 F0,首先是disas_insn()函數翻譯指令,當碰到第1個字節EA,分析可知這是一條16位無條件跳轉指令,
像上面說的jmp f000:e05b指令,它分解為如下微操作:
gen_op_movl_T0_im(selector);
gen_op_movl_T1_imu(offset);
gen_op_movl_seg_T0_vm(R_CS);
gen_op_movl_T0_T1();
gen_op_jmp_T0();
這幾條微操作的意義概括起來很簡單,就是把selector放到env.cs,把offset放到env.eip。
1 這幾個微指令函數的定義如下(功能可看注釋): 2 3 static inline void gen_op_movl_T0_im(int32_t val) 4 { 5 tcg_gen_movi_tl(cpu_T[0], val); // 相當於 cpu_T[0] = val 6 } 7 8 static inline void gen_op_movl_T1_imu(uint32_t val) 9 { 10 tcg_gen_movi_tl(cpu_T[1], val); // 相當於 cpu_T[1] = val 11 } 12 13 static inline void gen_op_movl_seg_T0_vm(int seg_reg) 14 { 15 tcg_gen_andi_tl(cpu_T[0], cpu_T[0], 0xffff); // cpu_T[0] = cpu_T[0]&0xffff 16 tcg_gen_st32_tl(cpu_T[0], cpu_env, 17 offsetof(CPUX86State,segs[seg_reg].selector)); // the value of cpu_T[0] store to the 'offset' of cpu_env 18 tcg_gen_shli_tl(cpu_T[0], cpu_T[0], 4); // cpu_T[0] = cpu_T[0]<<4 19 tcg_gen_st_tl(cpu_T[0], cpu_env, 20 offsetof(CPUX86State,segs[seg_reg].base)); // the value of cpu_T[0] store to the 'offset' of cpu_env 21 } 22 23 static inline void gen_op_movl_T0_T1(void) 24 { 25 tcg_gen_mov_tl(cpu_T[0], cpu_T[1]); // cpu_T[0] = cpu_T[1] 26 } 27 28 static inline void gen_op_jmp_T0(void) 29 { 30 tcg_gen_st_tl(cpu_T[0], cpu_env, offsetof(CPUState, eip)); // // the value of cpu_T[0] store to the 'offset' of cpu_env 31 } 32 33 其中,cpu_T[0]、cpu_T[1]和前面講過的T0、T1功能一樣,都是用來臨時存儲的變量。在32位目標機上,tcg_gen_movi_tl 就是 tcg_gen_op2i_i32 函數,它的定義如下: 34 tcg/tcg-op.h:2921: #define tcg_gen_movi_tl tcg_gen_movi_i32
35 static inline void tcg_gen_op2i_i32(int opc, TCGv_i32 arg1, TCGArg arg2) 36 { 37 *gen_opc_ptr++ = opc; 38 *gen_opparam_ptr++ = GET_TCGV_I32(arg1); 39 *gen_opparam_ptr++ = arg2; 40 } 41 42 static inline void tcg_gen_movi_i32(TCGv_i32 ret, int32_t arg) 43 { 44 tcg_gen_op2i_i32(INDEX_op_movi_i32, ret, arg); 45 } 46 47 gen_opparam_buf 是用來存放操作數的緩沖區,它的存放順序是:第1個4字節代表s->temps(用來存放目標值的數組,即輸出參數)的索引,
第2個4字節及之后字節代表輸入參數,對它的具體解析過程可見 tcg_reg_alloc_movi 函數,示例代碼如下: 48 49 TCGTemp *ots; 50 tcg_target_ulong val; 51 52 ots = &s->temps[args[0]]; 53 val = args[1]; 54 55 ots->val_type = TEMP_VAL_CONST; 56 ots->val = val; // 把輸入值暫時存放在ots結構中
jmp f000:e05b 生成的最終指令如下:
099D0040 B8 00 F0 00 00 mov eax,0F000h 099D0045 81 E0 FF FF 00 00 and eax,0FFFFh 099D004B 89 45 50 mov dword ptr [ebp+50h],eax 099D004E C1 E0 04 shl eax,4 099D0051 89 45 54 mov dword ptr [ebp+54h],eax 099D0054 B8 5B E0 00 00 mov eax,0E05Bh 099D0059 89 45 20 mov dword ptr [ebp+20h],eax 099D005C 31 C0 xor eax,eax 099D005E E9 25 5D CA 06 jmp _code_gen_prologue+8 (10675D88h) /* 返回 */
從上面可以看出,生成的Host代碼很簡潔,對於Target機的JMP,Host沒有去執行真正的跳轉指令,而只是簡單的將目標地址放到EIP中而已。
QEMU維護着一個稱為 CPUState 的數據結構,這個結構包括了Target機CPU的所有寄存器,像EAX,EBP,ESP,CS,EIP,EFLAGS等。
它總是代表着Target機的當前狀態,用env變量來表示 CPUState 結構,QEMU每次解析Target指令時,總是以 env.cs+env.eip 為開始地址的。
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是怎么聯系的, 在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 代碼分析:BIOS 的加載過程: http://www.ibm.com/developerworks/cn/linux/1410_qiaoly_qemubios/
