国产成人精品无码青草_亚洲国产美女精品久久久久∴_欧美人与鲁交大毛片免费_国产果冻豆传媒麻婆精东

15158846557 在線咨詢 在線咨詢
15158846557 在線咨詢
所在位置: 首頁 > 營銷資訊 > 網(wǎng)站運(yùn)營 > 從kvmtools學(xué)習(xí)虛擬化一 基礎(chǔ)介紹

從kvmtools學(xué)習(xí)虛擬化一 基礎(chǔ)介紹

時(shí)間:2023-07-12 18:48:01 | 來源:網(wǎng)站運(yùn)營

時(shí)間:2023-07-12 18:48:01 來源:網(wǎng)站運(yùn)營

從kvmtools學(xué)習(xí)虛擬化一 基礎(chǔ)介紹:

前言

qemu可謂是linux中虛擬化的集大成者, 但是qemu龐大的體量對于我這樣的初學(xué)者很不友好, 后有幸看到一本很不錯(cuò)的書: <<深度探索Linux系統(tǒng)虛擬化原理與實(shí)現(xiàn)>>, 其中使用一個(gè)輕量級的虛擬機(jī)kvmtool作為講解的案例, 通俗易懂.

在看完kvmtools了解虛擬化原理之后再去看qemu, 相信許多問題都會迎刃而解. 該書以kvmtool為案例, 本系列文章則反過來從kvmtool出發(fā), 去踐行學(xué)到的知識, 也算是自己的讀書筆記.

虛擬機(jī)基礎(chǔ)

內(nèi)核實(shí)際上也就是一段程序, 虛擬機(jī)的目標(biāo)就是運(yùn)行內(nèi)核, 那么如何實(shí)現(xiàn)這個(gè)目標(biāo)呢?

我們先打一個(gè)比方, 如果有一個(gè)人想要住在海邊, 但是買不起房子, 那么該怎么辦呢? 除了努力賺錢外, 他還可以把窗戶換成屏幕, 不停播放海的畫面, 播放海浪的聲音, 時(shí)間一久就真的好像住在海邊一樣.

我們把操作系統(tǒng)的內(nèi)核看做是這個(gè)人, 虛擬機(jī)就是窗戶上的屏幕, 內(nèi)核想要運(yùn)行在物理機(jī)上, 當(dāng)內(nèi)核想要訪問鍵盤時(shí), 虛擬機(jī)就會在屏幕上播放鍵盤相關(guān)信息來哄騙內(nèi)核, 這就是虛擬機(jī)的基本思想.

為了實(shí)現(xiàn)上述過程, 常用的是陷入和模擬(Trap and Emulate)模型. 我們知道CPU有用戶態(tài)和內(nèi)核態(tài)兩種, CPU指令也分配特權(quán)指令和非特權(quán)指令. 在陷入和模擬模型中, 虛擬機(jī)程序(VMM)運(yùn)行用戶態(tài), 內(nèi)核也運(yùn)行在用戶態(tài)中, 內(nèi)核中的非特權(quán)指令不需要干預(yù), CPU直接運(yùn)行即可. 對于特權(quán)指令, 由于CPU處于用戶態(tài), 會觸發(fā)虛擬機(jī)異常, 陷入VMM中, VMM代理虛擬機(jī)完成系統(tǒng)資源的訪問, 這就是模擬的過程. 這樣所有的硬件資源都被VMM控制, 不會破壞宿主機(jī)的環(huán)境.

上述過程是純軟件實(shí)現(xiàn)的虛擬化, 但并不是所有的特權(quán)指令都需要模擬, 也不是所有需要模擬的指令都是特權(quán)指令. 單純的依靠軟件來虛擬化十分困難. 為此Intel提出了硬件的解決方案VT-x, 為CPU增加了虛擬機(jī)擴(kuò)展, 簡稱為VMX.

當(dāng)CPU開啟VMX支持, CPU就有了兩種運(yùn)行模擬時(shí): VMX Root ModeVMX non-Root Mode, 每種模式都支持ring0~ring3. 宿主機(jī)(Host)的內(nèi)核和程序運(yùn)行在VMX Root Mode, 這與最普通的模式一樣. 虛擬機(jī)(Guest)運(yùn)行在VMX non Root Mode. 由于VMX的支持, 現(xiàn)在不需要特權(quán)壓縮, 虛擬機(jī)內(nèi)核可以運(yùn)行在VMX non Root Mode ring 0中. 虛擬機(jī)中的程序可以運(yùn)行在VMX non Root ring3中.

開啟VMX后, CPU會支持VMX指令集, 用于在Host與Guest之間進(jìn)行切換. 正如進(jìn)程之間切換需要任務(wù)狀態(tài)段來保存CPU狀態(tài)一樣. 在切入切出虛擬機(jī)時(shí), CPU使用VMCS(Virtual Machine Control data Structures)結(jié)構(gòu)來描述相關(guān)信息, VMCS包含切入虛擬機(jī)前宿主機(jī)的寄存器狀態(tài), 切入虛擬機(jī)后虛擬機(jī)的寄存器狀態(tài), 以及中斷等控制信息.

CPU的狀態(tài)轉(zhuǎn)換如下圖.




image.png



為了便于使用, linux中基于虛擬化指令集實(shí)現(xiàn)了KVM(Kernel-based Virtual Machine)模塊, 該模塊只負(fù)責(zé)CPU, 內(nèi)存, 中斷等最核心的虛擬化, 并通過linux的文件系統(tǒng)向用戶導(dǎo)出接口. 至于相關(guān)外設(shè), 如硬盤, pci設(shè)備等的虛擬化, 由于種類繁多, 代碼量大, 因此由kvmtool這類虛擬機(jī)管理軟件(VMM)來負(fù)責(zé). 由此我們可以看到kvmtool基本架構(gòu)如下




image.png



kvmtool是一個(gè)托管KVM虛擬機(jī)(后文稱之為guest)的輕量級工具. 作為單純的虛擬化工具, 只支持與宿主機(jī)(后文稱之為Host)相同架構(gòu)的Guest, 有一個(gè)特例: 32位的guest仍可以運(yùn)行在64位的host中.

kvmtool的目標(biāo)是提供一個(gè)簡單的, 輕量的 KVM host工具. 可用于啟動不依賴BIOS的Linux內(nèi)核的虛擬機(jī)鏡像, 并且只實(shí)現(xiàn)了最基礎(chǔ)的設(shè)備模擬. 如果想要涉足虛擬化的話, kvmtool很適合入門. 他只有5K多行的純C代碼.

由于KVM-base虛擬機(jī)是主流, 因此我們主要關(guān)注于kvmtool中設(shè)備模擬的部分, 至于KVM部分, 只會講一下api的使用和背后的原理. 接下來就通過kvmtool來探索linux虛擬化的世界吧

環(huán)境搭建

Parallel Desktop虛擬機(jī)中安裝Ubuntu 20.04, 因?yàn)榈桶姹镜膗buntu在kvm嵌套虛擬化時(shí)會出錯(cuò), 先安裝kvm:

sudo apt-get install qemu-kvmkvm-ok切換到kvmtools的主目錄, 直接make會報(bào)錯(cuò)

hw/i8042.c: In function kbd_io:hw/i8042.c:153:19: error: value may be used uninitialized in this function [-Werror=maybe-uninitialized] state.write_cmd = val; ~~~~~~~~~~~~~~~~^~~~~hw/i8042.c:298:5: note: value was declared here u8 value; ^~~~~cc1: all warnings being treated as errorskbd_io函數(shù)中為變量value設(shè)置一個(gè)初始值,

static void kbd_io(struct kvm_cpu *vcpu, u64 addr, u8 *data, u32 len, u8 is_write, void *ptr){ u8 value = 0 ; //set default value if (is_write) value = ioport__read8(data); switch (addr) { case I8042_COMMAND_REG: if (is_write) kbd_write_command(vcpu->kvm, value); else value = kbd_read_status(); break; ....為了調(diào)試方便, 在Makefile中把CFLAGS的-O2修改為-O0, 表示編譯時(shí)不進(jìn)行優(yōu)化. 然后make即可

CFLAGS += $(CPPFLAGS) $(DEFINES) -I$(KVM_INCLUDE) -I$(ARCH_INCLUDE) -O0 -fno-strict-aliasing -g啟虛擬機(jī)的命令如下, 其中rootfs.cpio為編譯busybox打包得到的文件鏡像, bzImage為linux內(nèi)核編譯之后得到鏡像

./lkvm run / --initrd ./rootfs.cpio / --kernel ./bzImage

入口點(diǎn): main()

main()在去掉argv[0]之后會調(diào)用handle_command()根據(jù)kvm_commands中支持的命令去解析參數(shù)

int main(int argc, char* argv[]){ ...; return handle_kvm_command(argc - 1, &argv[1]); //從第二個(gè)參數(shù)開始解析, 省略"lkvm"}static int handle_kvm_command(int argc, char** argv){ //進(jìn)行參數(shù)解析 return handle_command(kvm_commands, argc, (const char**)&argv[0]);}kvm_commands是一個(gè)struct cmd_struct組成的數(shù)組, 所有支持的命令如下

struct cmd_struct { const char* cmd; //命令名字 int (*fn)(int, const char**, const char*); //執(zhí)行命令 void (*help)(void); //獲取命令幫助 int option; //選項(xiàng)};struct cmd_struct kvm_commands[] = { { "pause", kvm_cmd_pause, kvm_pause_help, 0 }, //暫停虛擬機(jī)的運(yùn)行 { "resume", kvm_cmd_resume, kvm_resume_help, 0 }, //恢復(fù)虛擬機(jī)的運(yùn)行 { "debug", kvm_cmd_debug, kvm_debug_help, 0 }, //從一個(gè)正在運(yùn)行的虛擬機(jī)輸出調(diào)試信息 { "balloon", kvm_cmd_balloon, kvm_balloon_help, 0 }, //擴(kuò)張或者搜索virtio balloon { "list", kvm_cmd_list, kvm_list_help, 0 }, //輸出正在host上運(yùn)行的虛擬機(jī) { "version", kvm_cmd_version, NULL, 0 }, { "--version", kvm_cmd_version, NULL, 0 }, { "stop", kvm_cmd_stop, kvm_stop_help, 0 }, //停止一個(gè)運(yùn)行的虛擬機(jī)實(shí)例 { "stat", kvm_cmd_stat, kvm_stat_help, 0 }, //輸出正在運(yùn)行的虛擬機(jī)狀態(tài) { "help", kvm_cmd_help, NULL, 0 }, { "setup", kvm_cmd_setup, kvm_setup_help, 0 }, //設(shè)置一個(gè)新的虛擬機(jī) { "run", kvm_cmd_run, kvm_run_help, 0 }, //啟動一個(gè)虛擬機(jī) { "sandbox", kvm_cmd_sandbox, kvm_run_help, 0 }, //在虛擬機(jī)沙盒中運(yùn)行命令 { NULL, NULL, NULL, 0 },};handle_command()解析命令的過程如下.

struct cmd_struct* kvm_get_command(struct cmd_struct* command, const char* cmd){ struct cmd_struct* p = command; //遍歷所有可用命令, 返回cmd對應(yīng)的struct cmd_struct while (p->cmd) { if (!strcmp(p->cmd, cmd)) return p; p++; } return NULL;}int handle_command(struct cmd_struct* command, int argc, const char** argv){ struct cmd_struct* p; const char* prefix = NULL; int ret = 0; ...; //根據(jù)argv[0]找到對應(yīng)的struct cmd_struct p = kvm_get_command(command, argv[0]); //從&argv[1]開始作為參數(shù), 調(diào)用命令對應(yīng)的執(zhí)行函數(shù) ret = p->fn(argc - 1, &argv[1], prefix); return ret;}我們以run命令為例子, 根據(jù)kvm_commands可知, 接下來會進(jìn)入bulitin-run.c中的kvm_cmd_run()

處理run命令: kvm_cmd_run()

kvm_cmd_run()如下

int kvm_cmd_run(int argc, const char** argv, const char* prefix){ int ret = -EFAULT; struct kvm* kvm; //根據(jù)參數(shù)初始化一個(gè)kvm對象 kvm = kvm_cmd_run_init(argc, argv); //執(zhí)行一個(gè)虛擬機(jī) ret = kvm_cmd_run_work(kvm); //虛擬機(jī)退出 kvm_cmd_run_exit(kvm, ret); return ret;}

虛擬機(jī)初始化: kvm_cmd_run_init()

kvm_cmd_run_init()分為三部分

  1. 調(diào)用kvm__new()申請一個(gè)kvm對象
  2. 然后通過宏BUILD_OPTIONS生成所有可用的命令選項(xiàng), 然后調(diào)用parse_options()解析參數(shù) ,然后進(jìn)行一些參數(shù)的處理
  3. 通過init_list__init()調(diào)用所有初始化函數(shù)來初始化此kvm對象
static struct kvm* kvm_cmd_run_init(int argc, const char** argv){ static char default_name[20]; unsigned int nr_online_cpus; struct kvm* kvm = kvm__new(); //申請一個(gè)kvm對象 nr_online_cpus = sysconf(_SC_NPROCESSORS_ONLN); //獲取Host中有多少CPU while (argc != 0) { BUILD_OPTIONS(options, &kvm->cfg, kvm); //初始化options為run命令的參數(shù) //根據(jù)option解析argv參數(shù) argc = parse_options(argc, argv, options, run_usage, PARSE_OPT_STOP_AT_NON_OPTION | PARSE_OPT_KEEP_DASHDASH); ...; } kvm_run_validate_cfg(kvm); //檢查kvm中的配置 //cpu個(gè)數(shù)默認(rèn)為nr_online_cpus if (kvm->cfg.nrcpus == 0) kvm->cfg.nrcpus = nr_online_cpus; //沒設(shè)置內(nèi)存大小,就根據(jù)cpu個(gè)數(shù)計(jì)算 if (!kvm->cfg.ram_size) kvm->cfg.ram_size = get_ram_size(kvm->cfg.nrcpus); ...; //處理其余默認(rèn)配置 //遍歷初始化函數(shù)表, 以kvm為參數(shù)調(diào)用其中的所有函數(shù) if (init_list__init(kvm) < 0) die("Initialisation failed");}

申請kvm對象: kvm__new()

新建一個(gè)kvm對象的過程如下.

struct kvm* kvm__new(void){ struct kvm* kvm = calloc(1, sizeof(*kvm)); //分配內(nèi)存 if (!kvm) return ERR_PTR(-ENOMEM); mutex_init(&kvm->mem_banks_lock); //初始化互斥鎖 kvm->sys_fd = -1; kvm->vm_fd = -1; return kvm;}一個(gè)struct kvm就表示一臺虛擬機(jī), 定義如下, 其中字段比較多, 我們會在后面一點(diǎn)點(diǎn)介紹

struct kvm { struct kvm_arch arch; // Guest的架構(gòu) struct kvm_config cfg; // Guest配置相關(guān)信息 int sys_fd; /* 通過"/dev/kvm"打開kvm模塊的fd */ int vm_fd; /* 通過ioctl(sys_fd, KVM_CREATE_VM, ...)創(chuàng)建一個(gè)虛擬機(jī)時(shí)KVM返回的fd */ timer_t timerid; /* Posix timer for interrupts */ int nrcpus; /* 運(yùn)行多少個(gè)CPU */ struct kvm_cpu** cpus; // CPU對象數(shù)組 u32 mem_slots; /* 內(nèi)存插槽 for KVM_SET_USER_MEMORY_REGION */ u64 ram_size; // 內(nèi)存大小 void* ram_start; // Host中為Guest的內(nèi)存分配的地址 u64 ram_pagesize; // 內(nèi)存頁大小 struct mutex mem_banks_lock; struct list_head mem_banks; // Guest可能有多個(gè)內(nèi)存區(qū)域, 每一個(gè)內(nèi)存區(qū)域用struct kvm_mem_bank表示 bool nmi_disabled; bool msix_needs_devid; const char* vmlinux; //執(zhí)行內(nèi)核程序 struct disk_image** disks; //指向磁盤鏡像 int nr_disks; //有多少個(gè)磁盤 int vm_state; //虛擬機(jī)狀態(tài)#ifdef KVM_BRLOCK_DEBUG pthread_rwlock_t brlock_sem;#endif};

參數(shù)解析:parse_options()

BUILD_OPTIONS會生成一個(gè)struct option組成的數(shù)組, 下面只以-k為例子

//name: 命令的選項(xiàng)數(shù)組. //cfg: 指向struct kvm_config對象, 保存解析出來的配置//kvm: 指向struct kvm對象#define BUILD_OPTIONS(name, cfg, kvm) / struct option name[] = { / OPT_STRING('k', "kernel", &(cfg)->kernel_filename, "kernel", / "Kernel to boot in virtual machine"), / ... }其中每一個(gè)選項(xiàng)struct option定義如下

struct option { enum parse_opt_type type; //保存選項(xiàng)的類型, 最后一個(gè)參數(shù)必須設(shè)置為OPTION_END int short_name; //選項(xiàng)的短名, 只包含一個(gè)char, '/0'表示沒有 const char* long_name; //選項(xiàng)的長名 void* value; //指向?qū)懭脒@個(gè)選項(xiàng)對應(yīng)值的地方 const char* argh; const char* help; void* ptr; /* 選項(xiàng)標(biāo)志的掩碼: - PARSE_OPT_OPTARG: 這個(gè)選項(xiàng)是可選的 - PARSE_OPT_NOARG: 這個(gè)選項(xiàng)沒有參數(shù) - PARSE_OPT_NONEG: 這個(gè)選項(xiàng)不能是負(fù)的 - PARSE_OPT_HIDDEN: 展示默認(rèn)用法中會跳過這個(gè)選項(xiàng), 展示更長的用法時(shí)出現(xiàn) */ int flags; parse_opt_cb* callback; //對于OPTION_CALLBACK, 指向需要調(diào)用的回調(diào)函數(shù) intptr_t defval; //對于可選選項(xiàng), 該值作為默認(rèn)值寫入(*->value)中. 對于有回調(diào)的選項(xiàng)則隨意使用};OPT_STRING負(fù)責(zé)初始化一個(gè)option

#define OPT_STRING(s, l, v, a, h) / { / .type = OPTION_STRING, / .short_name = (s), / .long_name = (l), / .value = check_vtype(v, const char**), (a), / .help = (h) / }--kernel為例子, 宏展開時(shí)會生成OPT_STRING('k', "kernel", &(&kvm->cfg)->kernel_filename, "kernel", "..."), 在函數(shù)parse_options()解析argv的過程中就會把內(nèi)核文件名寫入&(&kvm->cfg)->kernel_filename中. 其余參數(shù)類似處理

本例中配置處理完畢后kvm->cfg的主要內(nèi)容如下




image.png



初始化鏈表: init_list__init()

kvmtool用struct init_item表示一個(gè)初始化函數(shù), 每個(gè)初始化函數(shù)都有一個(gè)優(yōu)先級, 優(yōu)先級相同的初始化函數(shù)通過struct hlist_node n組織為一個(gè)雙鏈表, 數(shù)組init_lists中保存各優(yōu)先級函數(shù)對應(yīng)的鏈表頭.

#define PRIORITY_LISTS 10static struct hlist_head init_lists[PRIORITY_LISTS]; //根據(jù)優(yōu)先級排序的初始化函數(shù)//表示一個(gè)要初始化的函數(shù)struct init_item { struct hlist_node n; // 用作非循環(huán)雙鏈表中的指針 const char* fn_name; //函數(shù)名 int (*init)(struct kvm*); //初始化函數(shù)};kvmtool定義了如下宏, 用于注冊各個(gè)優(yōu)先級的初始化函數(shù)

#define core_init(cb) __init_list_add(cb, 0) //核心初始化: 為Guest申請內(nèi)存和CPU#define base_init(cb) __init_list_add(cb, 2) //基礎(chǔ)初始化: 初始化CPU, 建立PCI總線#define dev_base_init(cb) __init_list_add(cb, 4) //基礎(chǔ)設(shè)備初始化: 建立中斷機(jī)制#define dev_init(cb) __init_list_add(cb, 5) //設(shè)備初始化: 建立鍵盤, 鼠標(biāo)等具體設(shè)備//cb: 回調(diào)函數(shù), l: 優(yōu)先級, 該宏會生成一個(gè)構(gòu)造函數(shù), 在進(jìn)入main之前就被調(diào)用, 從而完成init_lists的設(shè)置#define __init_list_add(cb, l) / static void __attribute__((constructor)) __init__##cb(void) / { / static char name[] = #cb; / static struct init_item t; / init_list_add(&t, cb, l, name); / }//將init_item初始化并添加到初始化鏈表中int init_list_add(struct init_item* t, int (*init)(struct kvm*), int priority, const char* name){ t->init = init; t->fn_name = name; hlist_add_head(&t->n, &init_lists[priority]); return 0;}函數(shù)init_list__init()會根據(jù)優(yōu)先級調(diào)用鏈表中所有的初始化函數(shù)

int init_list__init(struct kvm* kvm){ unsigned int i; int r = 0; struct init_item* t; for (i = 0; i < ARRAY_SIZE(init_lists); i++) //從低到高遍歷所有的優(yōu)先級 hlist_for_each_entry(t, &init_lists[i], n) //遍歷此優(yōu)先級鏈表中所有的函數(shù) { r = t->init(kvm); //調(diào)用該初始化函數(shù) if (r < 0) { pr_warning("Failed init: %s/n", t->fn_name); goto fail; } }fail: return r;}一些典型的注冊的初始化函數(shù)如下, 在接下來的文章中, 我們看一下這些初始化函數(shù)到底做了什么

core_init(kvm__init); //注冊為核心初始化函數(shù)base_init(kvm_cpu__init);dev_base_init(pci__init);dev_base_init(irq__init);

關(guān)鍵詞:基礎(chǔ),學(xué)習(xí),虛擬

74
73
25
news

版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。

為了最佳展示效果,本站不支持IE9及以下版本的瀏覽器,建議您使用谷歌Chrome瀏覽器。 點(diǎn)擊下載Chrome瀏覽器
關(guān)閉