日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

QEMU

發布時間:2023/12/20 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 QEMU 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

QEMU 1: 使用QEMU創建虛擬機

一、QEMU簡介

QEMU是一款開源的模擬器及虛擬機監管器(Virtual Machine Monitor, VMM)。QEMU主要提供兩種功能給用戶使用。一是作為用戶態模擬器,利用動態代碼翻譯機制來執行不同于主機架構的代碼。二是作為虛擬機監管器,模擬全系統,利用其他VMM(Xen, KVM, etc)來使用硬件提供的虛擬化支持,創建接近于主機性能的虛擬機。

用戶可以通過不同Linux發行版所帶有的軟件包管理器來安裝QEMU。如在Debian系列的發行版上可以使用下面的命令來安裝:

sudo apt-get install qemu

或者在紅帽系列的發行版上使用如下命令安裝:

sudo yum install qemu -y

除此之外,也可以選擇從源碼安裝。

獲取QEMU源碼

可以從QEMU官網上下載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

如果需要參與到QEMU的開發中,最好使用Git獲取源碼:

$git clone git://git.qemu-project.org/qemu.git

編譯及安裝

獲取源碼后,可以根據需求來配置和編譯QEMU。

$cd qemu-2.0.0 //如果使用的是git下載的源碼,執行cd qemu $./configure --enable-kvm --enable-debug --enable-vnc --enable-werror --target-list="x86_64-softmmu" $make -j8 $sudo make install

configure腳本用于生成Makefile,其選項可以用./configure --help查看。這里使用到的選項含義如下:

--enable-kvm:編譯KVM模塊,使QEMU可以利用KVM來訪問硬件提供的虛擬化服務。 --enable-vnc:啟用VNC--enalbe-werror:編譯時,將所有的警告當作錯誤處理。 --target-list:選擇目標機器的架構。默認是將所有的架構都編譯,但為了更快的完成編譯,指定需要的架構即可。

二、基本原理

QEMU作為系統模擬器時,會模擬出一臺能夠獨立運行操作系統的虛擬機。如下圖所示,每個虛擬機對應主機(Host)中的一個QEMU進程,而虛擬機的vCPU對應QEMU進程的一個線程。

系統虛擬化最主要是虛擬出CPU、內存及I/O設備。虛擬出的CPU稱之為vCPU,QEMU為了提升效率,借用KVM、XEN等虛擬化技術,直接利用硬件對虛擬化的支持,在主機上安全地運行虛擬機代碼(需要硬件支持)。虛擬機vCPU調用KVM的接口來執行任務的流程如下(代碼源自QEMU開發者Stefan的技術博客):

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發起ioctrl來調用KVM接口,KVM則利用硬件擴展直接將虛擬機代碼運行于主機之上,一旦vCPU需要操作設備寄存器,vCPU將會停止并退回到QEMU,QEMU去模擬出操作結果。

虛擬機內存會被映射到QEMU的進程地址空間,在啟動時分配。在虛擬機看來,QEMU所分配的主機上的虛擬地址空間為虛擬機的物理地址空間。

QEMU在主機用戶態模擬虛擬機的硬件設備,vCPU對硬件的操作結果會在用戶態進行模擬,如虛擬機需要將數據寫入硬盤,實際結果是將數據寫入到了主機中的一個鏡像文件中。

三、創建及使用虛擬機

命令行創建及啟動虛擬機

成功安裝QEMU之后便可創建自己的虛擬機。具體步驟如下:

1, 使用qemu-img創建虛擬機鏡像。虛擬機鏡像用來模擬虛擬機的硬盤,在啟動虛擬機之前需要創建鏡像文件。

[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最常用的鏡像格式,采用寫時復制技術來優化性能。fedora.img是鏡像文件的名字,10G是鏡像文件大小。鏡像文件創建完成后,可使用qemu-system-x86來啟動x86架構的虛擬機:

qemu-system-x86_64 fedora.img

此時會彈出一個窗口來作為虛擬機的顯示器,顯示內容如下:

因為fedora.img中并未給虛擬機安裝操作系統,所以會提示“No bootable device”,無可啟動設備。

2, 準備操作系統鏡像。

可以從不同Linux發行版的官方網站上獲取安裝鏡像,以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會導致性能損失。要使用KVM,首先要檢查硬件是否有虛擬化支持:

[kelvin@kelvin?~]$ grep -E 'vmx|svm' /proc/cpuinfo

如果有輸出則表示硬件有虛擬化支持。其次要檢查kvm模塊是否已經加載:

[kelvin@kelvin ~]$ lsmod | grep kvm kvm_intel 142999 0 kvm 444314 1 kvm_intel

如果kvm_intel/kvm_amd、kvm模塊被顯示出來,則kvm模塊已經加載。最后要確保qemu在編譯的時候使能了KVM,即在執行configure腳本的時候加入了–enable-kvm選項。

4, 啟動虛擬機安裝操作系統。

執行下面的命令啟動帶有cdrom的虛擬機:

[kelvin@kelvin?tmp]$ qemu-system-x86_64 -m 2048 -enable-kvm fedora.img -cdrom ./Fedora-Live-Desktop-x86_64-20-1.iso

-m 指定虛擬機內存大小,默認單位是MB, -enable-kvm使用KVM進行加速,-cdrom添加fedora的安裝鏡像。可在彈出的窗口中操作虛擬機,安裝操作系統,安裝完成后重起虛擬機便會從硬盤(fedora.img)啟動。之后再啟動虛擬機只需要執行:

[kelvin@kelvin?tmp]$ qemu-system-x86_64 -m 2048 -enable-kvm fedora.img

即可。

圖形界面創建及啟動虛擬機

命令行啟動虛擬機比較繁瑣,適合開發者,但對于普通用戶來說,采用圖形界面管理虛擬機則更為方便。采用圖形界面管理QEMU虛擬機需要安裝virt-manager,紅帽系列的發行版只需要執行命令:

$sudo yum install virt-manager -y

安裝完成后用root用戶啟動virt-manager:

$su - #virt-manager

啟動后的界面如下圖所示:

點擊左上角電腦圖標即可創建虛擬機。按照步驟操作即可完成對虛擬機的創建。



QEMU 2: 參數解析

一、使用gdb分析QEMU代碼

使用gdb不僅可以很好地調試代碼,也可以利用它來動態地分析代碼。使用gdb調試QEMU需要做一些準備工作:

1, 編譯QEMU時需要在執行configure腳本時的參數中加入–enable-debug。

2, 從QEMU官方網站上下載一個精簡的鏡像——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.bz2

3, 啟動gdb調試QEMU:

gdb --args qemu-system-x86_64 -enable-kvm -m 4096 -smp 4 linux-0.2.img

-smp指定處理器個數。

二、參數解析用到的數據結構

QEMU系統模擬的主函數位于vl.c文件,無論是qemu-system-x86_64還是qemu-system-ppc64,都是從vl.c中的main函數開始執行。下面先介紹main函數涉及到的一些數據結構。

QEMU鏈表

QEMU的鏈表在include/qemu/queue.h文件中定義,分為四種類型:

  • 單鏈表(singly-linked list):單鏈表適用于大的數據集,并且很少有刪除節點或者移動節點的操作,也適用于實現后進先出的隊列。
  • 鏈表(list):即雙向鏈表,除了頭節點之外每個節點都會同時指向前一個節點和后一個節點。
  • 簡單隊列(simple queue):簡單隊列類似于單鏈表,只是多了一個指向鏈表尾的一個表頭,插入節點的時候不僅可以像單鏈表那樣將其插入表頭或者某節點之后,還可以插入到鏈表尾。
  • 尾隊列(tail queue):類似于簡單隊列,但節點之間是雙向指向的。

這里不一一介紹各種鏈表的用法,只通過NotifierList的定義來說明QEMU鏈表(list)的用法。在main函數的開頭定義的DisplayState結構體使用到了NotifiereList,NotifierList就用到了鏈表。

a. 表頭及節點的定義

定義表頭需要用到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;

定義節點需要用到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 }

Notifier的節點定義如下:

21 struct Notifier22 {23 void (*notify)(Notifier *notifier, void *data);24 QLIST_ENTRY(Notifier) node;25 };

b. 初始化表頭

初始化表頭用到QLIST_INIT:

103 #define QLIST_INIT(head) do { \ 104 (head)->lh_first = NULL; \ 105 } while (/*CONSTCOND*/0)

初始化NotifierList就可以這樣進行:

19 void notifier_list_init(NotifierList *list)20 {21 QLIST_INIT(&list->notifiers);22 }

c. 在表頭插入節點

將節點插入到表頭使用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. 遍歷節點

遍歷節點使用QLIST_FOREACH或者QLIST_FOREACH_SAFE,QLIST_FOREACH_SAFE是為了防止遍歷過程中刪除了節點,從而導致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在執行所有的回調函數時就用到了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兩個數據結構。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中定義了幾個函數來對Error進行操作:

error_set //根據給定的ErrorClass以及格式化字符串來給Error分配空間并賦值 error_set_errno //除了error_set的功能外,將指定errno的錯誤信息追加到格式化字符串的后面 error_copy //復制Error error_is_set //判斷Error是否已經分配并設置 error_get_class //獲取Error的ErrorClass error_get_pretty //獲取Error的msg error_free //釋放Error及msg的空間

另外,QEMU定義了QError來處理更為細致的錯誤信息:

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來實現IO多路復用,關于GMainLoop可以參考博客GMainLoop的實現原理和代碼模型。由于GMainLoop并非QEMU本身的代碼,本文就不重復贅述。

三、QEMUOption、QemuOpt及QEMU參數解析

QEMU定義了QEMUOption來表示執行qemu-system-x86_64等命令時用到的選項。在vl.c中定義如下:

2123 typedef struct QEMUOption { 2124 const char *name; //選項名,如 -device, name的值就是device 2125 int flags; //標志位,表示選項是否帶參數,可以是0,或者HAS_ARG(值為0x0001) 2126 int index; //枚舉類型的值,如-device,該值就是QEMU_OPTION_device 2127 uint32_t arch_mask; // 選項支持架構的掩碼 2128 } QEMUOption;

vl.c中維護了一個QEMUOption數組qemu_options來存儲所有可用的選項,并利用qemu-options-wrapper.h和qemu-options.def來給該數組賦值。賦值語句如下:

2130 static const QEMUOption qemu_options[] = { 2131 { "h", 0, QEMU_OPTION_h, QEMU_ARCH_ALL }, 2132 #define QEMU_OPTIONS_GENERATE_OPTIONS 2133 #include "qemu-options-wrapper.h" 2134 { NULL }, 2135 };

#define QEMU_OPTIONS_GENERATE_OPTIONS選擇qemu-options-wrapper.h的操作,qemu-options-wrapper.h可以進行三種操作:

QEMU_OPTIONS_GENERATE_ENUM: 利用qemu-options.def生成一個枚舉值列表,就是上面提到的QEMU_OPTION_device等 QEMU_OPTIONS_GENERATE_HELP: 利用qemu-options.def生成幫助信息并輸出到標準輸出 QEMU_OPTIONS_GENERATE_OPTIONS: 利用qemu-options.def生成一組選項列表

可以通過下面的方法來展開qemu-options-wrapper.h來查看上述操作的結果,以生成選項為例。

  • 在qemu-options-wrapper.h第一行寫入#define QEMU_OPTIONS_GENERATE_OPTIONS.
  • 執行命令gcc -E -o options.txt qemu-options-wrapper.h
  • 查看文件options.txt即可
  • 給qemu_options數組賦值后,QEMU就有了一個所有可用選項的集合。之后在vl.c中main函數的一個for循環根據這個集合開始解析命令行。for循環的框架大致如下:

    1 for(;;) {2 if (optind >= argc)3 break;4 if (argv[optind][0] != '-') {5 hda_opts = drive_add(IF_DEFAULT, 0, argv[optind++], HD_OPTS);6 } else {7 const QEMUOption *popt;8 9 popt = lookup_opt(argc, argv, &optarg, &optind);10 if (!(popt->arch_mask & arch_type)) {11 printf("Option %s not supported for this target\n", popt->name);12 exit(1);13 }14 switch(popt->index) {15 case QEMU_OPTION_M:16 ......17 case QEMU_OPTION_hda:18 ...... 19 case QEMU_OPTION_watchdog:20 ......21 default:22 os_parse_cmd_args(popt->index, optarg);23 } 24 }25 }

    QEMU會把argv中以'-'開頭的字符串當作選項,然后調用lookup_opt函數到qemu_options數組中查找該選項,如果查找到的選項中flags的值是HAS_ARG,lookup_opt也會將參數字符串賦值給optarg。找到選項和參數之后,QEMU便根據選項中的index枚舉值來執行不同的分支。

    對于一些開關性質的選項,分支執行時僅僅是把相關的標志位賦值而已,如:

    3712 case QEMU_OPTION_old_param: 3713 old_param = 1; 3714 break;

    也有一些選項沒有子選項,分支執行時就直接把optarg的值交給相關變量:

    3822 case QEMU_OPTION_qtest: 3823 qtest_chrdev = optarg; 3824 break;

    對于那些擁有子選項的選項,如”-drive if=none,id=DRIVE-ID”,QEMU的處理會更為復雜一些。它會調用qemu_opts_parse來解析子選項,如realtime選項的解析:

    3852 case QEMU_OPTION_realtime: 3853 opts = qemu_opts_parse(qemu_find_opts("realtime"), optarg, 0); 3854 if (!opts) { 3855 exit(1); 3856 } 3857 configure_realtime(opts); 3858 break;

    對子選項的解析涉及到4個數據結構:QemuOpt, QemuDesc, QemuOpts, QemuOptsList. 它們的關系如下圖所示:

    QemuOpt存儲子選項,每個QemuOpt有一個QemuOptDesc來描述該子選項名字、類型、及幫助信息。兩個結構體定義如下:

    32 struct QemuOpt {33 const char *name; //子選項的名字34 const char *str; //字符串值35 36 const QemuOptDesc *desc; 37 union {38 bool boolean; //布爾值39 uint64_t uint; //數字或者大小40 } value; 41 42 QemuOpts *opts; 43 QTAILQ_ENTRY(QemuOpt) next;44 };95 typedef struct QemuOptDesc {96 const char *name;97 enum QemuOptType type;98 const char *help;99 } QemuOptDesc;

    子選項的類型可以是:

    88 enum QemuOptType {89 QEMU_OPT_STRING = 0, // 字符串90 QEMU_OPT_BOOL, // 取值可以是on或者off91 QEMU_OPT_NUMBER, // 數字92 QEMU_OPT_SIZE, // 大小,可以有K, M, G, T等后綴93 };

    QEMU維護了一個QemuOptsList*的數組,在util/qemu-config.c中定義:

    10 static QemuOptsList *vm_config_groups[32];

    在main函數中由qemu_add_opts將各種QemuOptsList寫入到數組中:

    2944 qemu_add_opts(&qemu_drive_opts); 2945 qemu_add_opts(&qemu_chardev_opts); 2946 qemu_add_opts(&qemu_device_opts); 2947 qemu_add_opts(&qemu_netdev_opts); 2948 qemu_add_opts(&qemu_net_opts); 2949 qemu_add_opts(&qemu_rtc_opts); 2950 qemu_add_opts(&qemu_global_opts); 2951 qemu_add_opts(&qemu_mon_opts); 2952 qemu_add_opts(&qemu_trace_opts); 2953 qemu_add_opts(&qemu_option_rom_opts); 2954 qemu_add_opts(&qemu_machine_opts); 2955 qemu_add_opts(&qemu_smp_opts); 2956 qemu_add_opts(&qemu_boot_opts); 2957 qemu_add_opts(&qemu_sandbox_opts); 2958 qemu_add_opts(&qemu_add_fd_opts); 2959 qemu_add_opts(&qemu_object_opts); 2960 qemu_add_opts(&qemu_tpmdev_opts); 2961 qemu_add_opts(&qemu_realtime_opts); 2962 qemu_add_opts(&qemu_msg_opts);

    每個QemuOptsList存儲了大選項所支持的所有小選項,如-realtime大選項QemuOptsList的定義:

    507 static QemuOptsList qemu_realtime_opts = {508 .name = "realtime",509 .head = QTAILQ_HEAD_INITIALIZER(qemu_realtime_opts.head),510 .desc = {511 {512 .name = "mlock",513 .type = QEMU_OPT_BOOL,514 },515 { /* end of list */ }516 },517 };

    -realtime只支持1個子選項,且值為bool類型,即只能是on或者off。

    在調用qemu_opts_parse解析子選項之前,QEMU會調用qemu_find_opts(“realtime”),把QemuOptsList *從qemu_add_opts中找出來,和optarg一起傳遞給qemu_opts_parse去解析。QEMU可能會多次使用同一個大選項來指定多個相同的設備,在這種情況下,需要用id來區分。QemuOpts結構體就表示同一id下所有的子選項,定義如下:

    46 struct QemuOpts {47 char *id;48 QemuOptsList *list;49 Location loc;50 QTAILQ_HEAD(QemuOptHead, QemuOpt) head;51 QTAILQ_ENTRY(QemuOpts) next;52 };

    其中list是同一個大選項下不同id的QemuOpts鏈表。


    總結

    以上是生活随笔為你收集整理的QEMU的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。