QEMU介绍
一、QEMU簡介
QEMU是一款開源的模擬器及虛擬機監(jiān)管器(Virtual Machine Monitor, VMM)。QEMU主要提供兩種功能給用戶使用。一是作為用戶態(tài)模擬器,利用動態(tài)代碼翻譯機制來執(zhí)行不同于主機架構(gòu)的代碼。二是作為虛擬機監(jiān)管器,模擬全系統(tǒng),利用其他VMM(Xen, KVM, etc)來使用硬件提供的虛擬化支持,創(chuàng)建接近于主機性能的虛擬機。
用戶可以通過不同Linux發(fā)行版所帶有的軟件包管理器來安裝QEMU。如在Debian系列的發(fā)行版上可以使用下面的命令來安裝:
sudo apt-get install qemu
除此之外,也可以選擇從源碼安裝。
獲取QEMU源碼
可以從QEMU下載官網(wǎng)上下載QEMU源碼的tar包,以命令行下載2.0版本的QEMU為例:
$wget http://wiki.qemu-project.org/download/qemu-2.0.0.tar.bz2 $tar xjvf qemu-2.0.0.tar.bz2編譯及安裝
獲取源碼后,可以根據(jù)需求來配置和編譯QEMU。
$cd qemu-2.0.0 //如果使用的是git下載的源碼,執(zhí)行cd qemu $./configure --enable-kvm --enable-debug --enable-vnc --enable-werror --target-list="x86_64-softmmu" 或者用戶模式(使能TCI)$./configure --target-list=arm-linux-user --enable-tcg-interpreter $make -j8 $sudo make installconfigure腳本用于生成Makefile,其選項可以用./configure --help查看。這里使用到的選項含義如下
--enable-kvm:編譯KVM模塊,使QEMU可以利用KVM來訪問硬件提供的虛擬化服務(wù)。 --enable-vnc:啟用VNC。 --enalbe-werror:編譯時,將所有的警告當(dāng)作錯誤處理。 --target-list:選擇目標(biāo)機器的架構(gòu)。默認(rèn)是將所有的架構(gòu)都編譯,但為了更快的完成編譯,指定需要的架構(gòu)即可。二、基本原理
QEMU作為系統(tǒng)模擬器時,會模擬出一臺能夠獨立運行操作系統(tǒng)的虛擬機。如下圖所示,每個虛擬機對應(yīng)主機(Host)中的一個QEMU進(jìn)程,而虛擬機的vCPU對應(yīng)QEMU進(jìn)程的一個線程。
系統(tǒng)虛擬化最主要是虛擬出CPU、內(nèi)存及I/O設(shè)備。虛擬出的CPU稱之為vCPU,QEMU為了提升效率,借用KVM、XEN等虛擬化技術(shù),直接利用硬件對虛擬化的支持,在主機上安全地運行虛擬機代碼(需要硬件支持)。虛擬機vCPU調(diào)用KVM的接口來執(zhí)行任務(wù)的流程如下(代碼源自QEMU開發(fā)者Stefan的技術(shù)博客):
open("/dev/kvm") ioctl(KVM_CREATE_VM) ioctl(KVM_CREATE_VCPU) for (;;) {ioctl(KVM_RUN)switch (exit_reason) {case KVM_EXIT_IO: /* ... */case KVM_EXIT_HLT: /* ... */} }QEMU發(fā)起ioctrl來調(diào)用KVM接口,KVM則利用硬件擴展直接將虛擬機代碼運行于主機之上,一旦vCPU需要操作設(shè)備寄存器,vCPU將會停止并退回到QEMU,QEMU去模擬出操作結(jié)果。
虛擬機內(nèi)存會被映射到QEMU的進(jìn)程地址空間,在啟動時分配。在虛擬機看來,QEMU所分配的主機上的虛擬地址空間為虛擬機的物理地址空間。
QEMU在主機用戶態(tài)模擬虛擬機的硬件設(shè)備,vCPU對硬件的操作結(jié)果會在用戶態(tài)進(jìn)行模擬,如虛擬機需要將數(shù)據(jù)寫入硬盤,實際結(jié)果是將數(shù)據(jù)寫入到了主機中的一個鏡像文件中。
三、創(chuàng)建及使用虛擬機
命令行創(chuàng)建及啟動虛擬機
成功安裝QEMU之后便可創(chuàng)建自己的虛擬機。具體步驟如下:
1, 使用qemu-img創(chuàng)建虛擬機鏡像。虛擬機鏡像用來模擬虛擬機的硬盤,在啟動虛擬機之前需要創(chuàng)建鏡像文件。
[kelvin@kelvin tmp]$ qemu-img create -f qcow2 fedora.img 10G Formatting 'fedora.img', fmt=qcow2 size=10737418240 encryption=off cluster_size=65536 lazy_refcounts=off [kelvin@kelvin tmp]$ ls fedora.img-f選項用于指定鏡像的格式,qcow2格式是QEMU最常用的鏡像格式,采用寫時復(fù)制技術(shù)來優(yōu)化性能。fedora.img是鏡像文件的名字,10G是鏡像文件大小。鏡像文件創(chuàng)建完成后,可使用qemu-system-x86來啟動x86架構(gòu)的虛擬機:
qemu-system-x86_64 fedora.img
此時會彈出一個窗口來作為虛擬機的顯示器,顯示內(nèi)容如下:
因為fedora.img中并未給虛擬機安裝操作系統(tǒng),所以會提示“No bootable device”,無可啟動設(shè)備。
2, 準(zhǔn)備操作系統(tǒng)鏡像。
可以從不同Linux發(fā)行版的官方網(wǎng)站上獲取安裝鏡像,以fedora20為例:
[kelvin@kelvin?tmp]$ wget http://ftp6.sjtu.edu.cn/fedora/linux/releases/20/Live/x86_64/Fedora-Live-Desktop-x86_64-20-1.iso
3, 檢查KVM是否可用。
QEMU使用KVM來提升虛擬機性能,如果不啟用KVM會導(dǎo)致性能損失。要使用KVM,首先要檢查硬件是否有虛擬化支持:
[kelvin@kelvin?~]$ grep -E 'vmx|svm' /proc/cpuinfo
如果有輸出則表示硬件有虛擬化支持。其次要檢查kvm模塊是否已經(jīng)加載:
[kelvin@kelvin ~]$ lsmod | grep kvm kvm_intel 142999 0 kvm 444314 1 kvm_intel如果kvm_intel/kvm_amd、kvm模塊被顯示出來,則kvm模塊已經(jīng)加載。最后要確保qemu在編譯的時候使能了KVM,即在執(zhí)行configure腳本的時候加入了–enable-kvm選項。
4, 啟動虛擬機安裝操作系統(tǒng)。
執(zhí)行下面的命令啟動帶有cdrom的虛擬機:
[kelvin@kelvin?tmp]$ qemu-system-x86_64 -m 2048 -enable-kvm fedora.img -cdrom ./Fedora-Live-Desktop-x86_64-20-1.iso
-m 指定虛擬機內(nèi)存大小,默認(rèn)單位是MB, -enable-kvm使用KVM進(jìn)行加速,-cdrom添加fedora的安裝鏡像。可在彈出的窗口中操作虛擬機,安裝操作系統(tǒng),安裝完成后重起虛擬機便會從硬盤(fedora.img)啟動。之后再啟動虛擬機只需要執(zhí)行:
[kelvin@kelvin?tmp]$ qemu-system-x86_64 -m 2048 -enable-kvm fedora.img
即可。
圖形界面創(chuàng)建及啟動虛擬機
命令行啟動虛擬機比較繁瑣,適合開發(fā)者,但對于普通用戶來說,采用圖形界面管理虛擬機則更為方便。采用圖形界面管理QEMU虛擬機需要安裝virt-manager,紅帽系列的發(fā)行版只需要執(zhí)行命令:
$sudo yum install virt-manager -y
安裝完成后用root用戶啟動virt-manager:
$su - #virt-manager啟動后的界面如下圖所示:
點擊左上角電腦圖標(biāo)即可創(chuàng)建虛擬機。按照步驟操作即可完成對虛擬機的創(chuàng)建。
QEMU 2: 參數(shù)解析
一、使用gdb分析QEMU代碼
使用gdb不僅可以很好地調(diào)試代碼,也可以利用它來動態(tài)地分析代碼。使用gdb調(diào)試QEMU需要做一些準(zhǔn)備工作:
1, 編譯QEMU時需要在執(zhí)行configure腳本時的參數(shù)中加入–enable-debug。
2, 從QEMU官方網(wǎng)站上下載一個精簡的鏡像——linux-0.2.img。linux-0.2.img只有8MB大小,啟動后包含一些常用的shell命令,用于QEMU的測試。
$wget http://wiki.qemu.org/download/linux-0.2.img.bz2 $bzip2 -d ./linux-0.2.img.bz23, 啟動gdb調(diào)試QEMU:
gdb --args qemu-system-x86_64 -enable-kvm -m 4096 -smp 4 linux-0.2.img
-smp指定處理器個數(shù)。
二、參數(shù)解析用到的數(shù)據(jù)結(jié)構(gòu)
QEMU系統(tǒng)模擬的主函數(shù)位于vl.c文件,無論是qemu-system-x86_64還是qemu-system-ppc64,都是從vl.c中的main函數(shù)開始執(zhí)行。下面先介紹main函數(shù)涉及到的一些數(shù)據(jù)結(jié)構(gòu)。
QEMU鏈表
QEMU的鏈表在include/qemu/queue.h文件中定義,分為四種類型:
- 單鏈表(singly-linked list):單鏈表適用于大的數(shù)據(jù)集,并且很少有刪除節(jié)點或者移動節(jié)點的操作,也適用于實現(xiàn)后進(jìn)先出的隊列。
- 鏈表(list):即雙向鏈表,除了頭節(jié)點之外每個節(jié)點都會同時指向前一個節(jié)點和后一個節(jié)點。
- 簡單隊列(simple queue):簡單隊列類似于單鏈表,只是多了一個指向鏈表尾的一個表頭,插入節(jié)點的時候不僅可以像單鏈表那樣將其插入表頭或者某節(jié)點之后,還可以插入到鏈表尾。
- 尾隊列(tail queue):類似于簡單隊列,但節(jié)點之間是雙向指向的。
這里不一一介紹各種鏈表的用法,只通過NotifierList的定義來說明QEMU鏈表(list)的用法。在main函數(shù)的開頭定義的DisplayState結(jié)構(gòu)體使用到了NotifiereList,NotifierList就用到了鏈表。
a. 表頭及節(jié)點的定義
定義表頭需要用到QLIST_HEAD,定義如下:
86 #define QLIST_HEAD(name, type) \87 struct name { \88 struct type *lh_first; /* first element */ \89 }NotifierList就采用了QLIST_HEAD來定義表頭:
27 typedef struct NotifierList28 {29 QLIST_HEAD(, Notifier) notifiers;30 } NotifierList;定義節(jié)點需要用到QLIST_ENTRY,定義如下:
94 #define QLIST_ENTRY(type) \95 struct { \96 struct type *le_next; /* next element */ \97 struct type **le_prev; /* address of previous next element */ \98 } b. 初始化表頭初始化表頭用到QLIST_INIT:103 #define QLIST_INIT(head) do { \ 104 (head)->lh_first = NULL; \ 105 } while (/*CONSTCOND*/0) 初始化NotifierList就可以這樣進(jìn)行:19 void notifier_list_init(NotifierList *list)20 {21 QLIST_INIT(&list->notifiers);22 } c. 在表頭插入節(jié)點將節(jié)點插入到表頭使用QLIST_INSERT_HEAD:122 #define QLIST_INSERT_HEAD(head, elm, field) do { \ 123 if (((elm)->field.le_next = (head)->lh_first) != NULL) \ 124 (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ 125 (head)->lh_first = (elm); \ 126 (elm)->field.le_prev = &(head)->lh_first; \ 127 } while (/*CONSTCOND*/0) 插入Notifier到NotifierList:24 void notifier_list_add(NotifierList *list, Notifier *notifier)25 {26 QLIST_INSERT_HEAD(&list->notifiers, notifier, node);27 } d. 遍歷節(jié)點遍歷節(jié)點使用QLIST_FOREACH或者QLIST_FOREACH_SAFE,QLIST_FOREACH_SAFE是為了防止遍歷過程中刪除了節(jié)點,從而導(dǎo)致le_next被釋放掉,中斷了遍歷。147 #define QLIST_FOREACH(var, head, field) \ 148 for ((var) = ((head)->lh_first); \ 149 (var); \ 150 (var) = ((var)->field.le_next)) 151 152 #define QLIST_FOREACH_SAFE(var, head, field, next_var) \ 153 for ((var) = ((head)->lh_first); \ 154 (var) && ((next_var) = ((var)->field.le_next), 1); \ 155 (var) = (next_var)) NotifierList在執(zhí)行所有的回調(diào)函數(shù)時就用到了QLIST_FOREACH_SAFE:34 void notifier_list_notify(NotifierList *list, void *data)35 {36 Notifier *notifier, *next;37 38 QLIST_FOREACH_SAFE(notifier, &list->notifiers, node, next) {39 notifier->notify(notifier, data);40 }41 } Error和QError 為了方便的處理錯誤信息,QEMU定義了Error和QError兩個數(shù)據(jù)結(jié)構(gòu)。Error在qobject/qerror.c中定義:101 struct Error 102 { 103 char *msg; 104 ErrorClass err_class; 105 }; 包含了錯誤消息字符串和枚舉類型的錯誤類別。錯誤類別有下面幾個:139 typedef enum ErrorClass140 {141 ERROR_CLASS_GENERIC_ERROR = 0,142 ERROR_CLASS_COMMAND_NOT_FOUND = 1,143 ERROR_CLASS_DEVICE_ENCRYPTED = 2,144 ERROR_CLASS_DEVICE_NOT_ACTIVE = 3,145 ERROR_CLASS_DEVICE_NOT_FOUND = 4,146 ERROR_CLASS_K_V_M_MISSING_CAP = 5,147 ERROR_CLASS_MAX = 6,148 } ErrorClass; QEMU在util/error.c中定義了幾個函數(shù)來對Error進(jìn)行操作:error_set //根據(jù)給定的ErrorClass以及格式化字符串來給Error分配空間并賦值 error_set_errno //除了error_set的功能外,將指定errno的錯誤信息追加到格式化字符串的后面 error_copy //復(fù)制Error error_is_set //判斷Error是否已經(jīng)分配并設(shè)置 error_get_class //獲取Error的ErrorClass error_get_pretty //獲取Error的msg error_free //釋放Error及msg的空間 另外,QEMU定義了QError來處理更為細(xì)致的錯誤信息:22 typedef struct QError { 23 QObject_HEAD;24 Location loc;25 char *err_msg;26 ErrorClass err_class;27 } QError; QError可以通過一系列的宏來給err_msg及err_class賦值:39 #define QERR_ADD_CLIENT_FAILED \40 ERROR_CLASS_GENERIC_ERROR, "Could not add client"41 42 #define QERR_AMBIGUOUS_PATH \43 ERROR_CLASS_GENERIC_ERROR, "Path '%s' does not uniquely identify an object"44 45 #define QERR_BAD_BUS_FOR_DEVICE \46 ERROR_CLASS_GENERIC_ERROR, "Device '%s' can't go on a %s bus"47 48 #define QERR_BASE_NOT_FOUND \49 ERROR_CLASS_GENERIC_ERROR, "Base '%s' not found" ... Location記錄了出錯的位置,定義如下:20 typedef struct Location {21 /* all members are private to qemu-error.c */22 enum { LOC_NONE, LOC_CMDLINE, LOC_FILE } kind;23 int num;24 const void *ptr;25 struct Location *prev;26 } Location; GMainLoop QEMU使用glib中的GMainLoop來實現(xiàn)IO多路復(fù)用,關(guān)于GMainLoop可以參考博客GMainLoop的實現(xiàn)原理和代碼模型。由于GMainLoop并非QEMU本身的代碼,本文就不重復(fù)贅述。?
?
?
?
?
?
?
?
?
?
?
?
?
?
總結(jié)
- 上一篇: 什么是软件危机?它有哪些典型表现?为什么
- 下一篇: 在读大学生创业历程,350元起家到现在将