您好,登錄后才能下訂單哦!
這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)虛擬化原理及QEMU啟動(dòng)過(guò)程是怎樣的,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
第一步,獲取到kvm句柄 kvmfd = open("/dev/kvm", O_RDWR); 第二步,創(chuàng)建虛擬機(jī),獲取到虛擬機(jī)句柄。 vmfd = ioctl(kvmfd, KVM_CREATE_VM, 0); 第三步,為虛擬機(jī)映射內(nèi)存,還有其他的PCI,信號(hào)處理的初始化。 ioctl(kvmfd, KVM_SET_USER_MEMORY_REGION, &mem); 第四步,將虛擬機(jī)鏡像映射到內(nèi)存,相當(dāng)于物理機(jī)的boot過(guò)程,把鏡像映射到內(nèi)存。 第五步,創(chuàng)建vCPU,并為vCPU分配內(nèi)存空間。 ioctl(kvmfd, KVM_CREATE_VCPU, vcpuid); vcpu->kvm_run_mmap_size = ioctl(kvm->dev_fd, KVM_GET_VCPU_MMAP_SIZE, 0); 第五步,創(chuàng)建vCPU個(gè)數(shù)的線程并運(yùn)行虛擬機(jī)。 ioctl(kvm->vcpus->vcpu_fd, KVM_RUN, 0); 第六步,線程進(jìn)入循環(huán),并捕獲虛擬機(jī)退出原因,做相應(yīng)的處理。 這里的退出并不一定是虛擬機(jī)關(guān)機(jī),虛擬機(jī)如果遇到IO操作,訪問(wèn)硬件設(shè)備,缺頁(yè)中斷等都會(huì)退出執(zhí)行,退出執(zhí)行可以理解為將CPU執(zhí)行上下文返回到QEMU。
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: /* ... */ } }
關(guān)于KVM_CREATE_VM參數(shù)的描述,創(chuàng)建的VM是沒(méi)有cpu和內(nèi)存的,需要QEMU進(jìn)程利用mmap系統(tǒng)調(diào)用映射一塊內(nèi)存給VM的描述符,其實(shí)也就是給VM創(chuàng)建內(nèi)存的過(guò)程。
KVM ioctl接口文檔
下面是一個(gè)KVM的簡(jiǎn)單demo,其目的在于加載 code 并使用KVM運(yùn)行起來(lái).
這是一個(gè)at&t的8086匯編,.code16表示他是一個(gè)16位的,當(dāng)然直接運(yùn)行是運(yùn)行不起來(lái)的,為了讓他運(yùn)行起來(lái),我們可以用KVM提供的API,將這個(gè)程序看做一個(gè)最簡(jiǎn)單的操作系統(tǒng),讓其運(yùn)行起來(lái)。
這個(gè)匯編的作用是輸出al寄存器的值到0x3f8端口。對(duì)于x86架構(gòu)來(lái)說(shuō),通過(guò)IN/OUT指令訪問(wèn)。PC架構(gòu)一共有65536個(gè)8bit的I/O端口,組成64KI/O地址空間,編號(hào)從0~0xFFFF。連續(xù)兩個(gè)8bit的端口可以組成一個(gè)16bit的端口,連續(xù)4個(gè)組成一個(gè)32bit的端口。I/O地址空間和CPU的物理地址空間是兩個(gè)不同的概念,例如I/O地址空間為64K,一個(gè)32bit的CPU物理地址空間是4G。
最終程序理想的輸出應(yīng)該是,al,bl的值后面KVM初始化的時(shí)候有賦值。
4\n (并不直接輸出\n,而是換了一行),hlt 指令表示虛擬機(jī)退出
.globl _start .code16 _start: mov $0x3f8, %dx add %bl, %al add $'0', %al out %al, (%dx) mov $'\n', %al out %al, (%dx) hlt
我們編譯一下這個(gè)匯編,得到一個(gè) Bin.bin 的二進(jìn)制文件
as -32 bin.S -o bin.o ld -m elf_i386 --oformat binary -N -e _start -Ttext 0x10000 -o Bin.bin bin.o
查看一下二進(jìn)制格式
? demo1 hexdump -C bin.bin 00000000 ba f8 03 00 d8 04 30 ee b0 0a ee f4 |......0.....| 0000000c 對(duì)應(yīng)了下面的code數(shù)組,這樣直接加載字節(jié)碼就不需要再?gòu)奈募虞d了 const uint8_t code[] = { 0xba, 0xf8, 0x03, /* mov $0x3f8, %dx */ 0x00, 0xd8, /* add %bl, %al */ 0x04, '0', /* add $'0', %al */ 0xee, /* out %al, (%dx) */ 0xb0, '\n', /* mov $'\n', %al */ 0xee, /* out %al, (%dx) */ 0xf4, /* hlt */ };
#include <err.h> #include <fcntl.h> #include <linux/kvm.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> int main(void) { int kvm, vmfd, vcpufd, ret; const uint8_t code[] = { 0xba, 0xf8, 0x03, /* mov $0x3f8, %dx */ 0x00, 0xd8, /* add %bl, %al */ 0x04, '0', /* add $'0', %al */ 0xee, /* out %al, (%dx) */ 0xb0, '\n', /* mov $'\n', %al */ 0xee, /* out %al, (%dx) */ 0xf4, /* hlt */ }; uint8_t *mem; struct kvm_sregs sregs; size_t mmap_size; struct kvm_run *run; // 獲取 kvm 句柄 kvm = open("/dev/kvm", O_RDWR | O_CLOEXEC); if (kvm == -1) err(1, "/dev/kvm"); // 確保是正確的 API 版本 ret = ioctl(kvm, KVM_GET_API_VERSION, NULL); if (ret == -1) err(1, "KVM_GET_API_VERSION"); if (ret != 12) errx(1, "KVM_GET_API_VERSION %d, expected 12", ret); // 創(chuàng)建一虛擬機(jī) vmfd = ioctl(kvm, KVM_CREATE_VM, (unsigned long)0); if (vmfd == -1) err(1, "KVM_CREATE_VM"); // 為這個(gè)虛擬機(jī)申請(qǐng)內(nèi)存,并將代碼(鏡像)加載到虛擬機(jī)內(nèi)存中 mem = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (!mem) err(1, "allocating guest memory"); memcpy(mem, code, sizeof(code)); // 為什么從 0x1000 開(kāi)始呢,因?yàn)轫?yè)表空間的前4K是留給頁(yè)表目錄 struct kvm_userspace_memory_region region = { .slot = 0, .guest_phys_addr = 0x1000, .memory_size = 0x1000, .userspace_addr = (uint64_t)mem, }; // 設(shè)置 KVM 的內(nèi)存區(qū)域 ret = ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, ®ion); if (ret == -1) err(1, "KVM_SET_USER_MEMORY_REGION"); // 創(chuàng)建虛擬CPU vcpufd = ioctl(vmfd, KVM_CREATE_VCPU, (unsigned long)0); if (vcpufd == -1) err(1, "KVM_CREATE_VCPU"); // 獲取 KVM 運(yùn)行時(shí)結(jié)構(gòu)的大小 ret = ioctl(kvm, KVM_GET_VCPU_MMAP_SIZE, NULL); if (ret == -1) err(1, "KVM_GET_VCPU_MMAP_SIZE"); mmap_size = ret; if (mmap_size < sizeof(*run)) errx(1, "KVM_GET_VCPU_MMAP_SIZE unexpectedly small"); // 將 kvm run 與 vcpu 做關(guān)聯(lián),這樣能夠獲取到kvm的運(yùn)行時(shí)信息 run = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, vcpufd, 0); if (!run) err(1, "mmap vcpu"); // 獲取特殊寄存器 ret = ioctl(vcpufd, KVM_GET_SREGS, &sregs); if (ret == -1) err(1, "KVM_GET_SREGS"); // 設(shè)置代碼段為從地址0處開(kāi)始,我們的代碼被加載到了0x0000的起始位置 sregs.cs.base = 0; sregs.cs.selector = 0; // KVM_SET_SREGS 設(shè)置特殊寄存器 ret = ioctl(vcpufd, KVM_SET_SREGS, &sregs); if (ret == -1) err(1, "KVM_SET_SREGS"); // 設(shè)置代碼的入口地址,相當(dāng)于32位main函數(shù)的地址,這里16位匯編都是由0x1000處開(kāi)始。 // 如果是正式的鏡像,那么rip的值應(yīng)該是類似引導(dǎo)扇區(qū)加載進(jìn)來(lái)的指令 struct kvm_regs regs = { .rip = 0x1000, .rax = 2, // 設(shè)置 ax 寄存器初始值為 2 .rbx = 2, // 同理 .rflags = 0x2, // 初始化flags寄存器,x86架構(gòu)下需要設(shè)置,否則會(huì)粗錯(cuò) }; ret = ioctl(vcpufd, KVM_SET_REGS, ®s); if (ret == -1) err(1, "KVM_SET_REGS"); // 開(kāi)始運(yùn)行虛擬機(jī),如果是qemu-kvm,會(huì)用一個(gè)線程來(lái)執(zhí)行這個(gè)vCPU,并加載指令 while (1) { // 開(kāi)始運(yùn)行虛擬機(jī) ret = ioctl(vcpufd, KVM_RUN, NULL); if (ret == -1) err(1, "KVM_RUN"); // 獲取虛擬機(jī)退出原因 switch (run->exit_reason) { case KVM_EXIT_HLT: puts("KVM_EXIT_HLT"); return 0; // 匯編調(diào)用了 out 指令,vmx 模式下不允許執(zhí)行這個(gè)操作,所以 // 將操作權(quán)切換到了宿主機(jī),切換的時(shí)候會(huì)將上下文保存到VMCS寄存器 // 后面CPU虛擬化會(huì)講到這部分 // 因?yàn)樘摂M機(jī)的內(nèi)存宿主機(jī)能夠直接讀取到,所以直接在宿主機(jī)上獲取到 // 虛擬機(jī)的輸出(out指令),這也是后面PCI設(shè)備虛擬化的一個(gè)基礎(chǔ),DMA模式的PCI設(shè)備 case KVM_EXIT_IO: if (run->io.direction == KVM_EXIT_IO_OUT && run->io.size == 1 && run->io.port == 0x3f8 && run->io.count == 1) putchar(*(((char *)run) + run->io.data_offset)); else errx(1, "unhandled KVM_EXIT_IO"); break; case KVM_EXIT_FAIL_ENTRY: errx(1, "KVM_EXIT_FAIL_ENTRY: hardware_entry_failure_reason = 0x%llx", (unsigned long long)run->fail_entry.hardware_entry_failure_reason); case KVM_EXIT_INTERNAL_ERROR: errx(1, "KVM_EXIT_INTERNAL_ERROR: suberror = 0x%x", run->internal.suberror); default: errx(1, "exit_reason = 0x%x", run->exit_reason); } } }
編譯并運(yùn)行這個(gè)demo
gcc -g demo.c -o demo ? demo1 ./demo 4 KVM_EXIT_HLT
IBM的徐同學(xué)有做過(guò)介紹,在此基礎(chǔ)上我再詳細(xì)介紹一下qemu-kvm的啟動(dòng)過(guò)程。
.globl _start .code16 _start: xorw %ax, %ax # 將 ax 寄存器清零 loop1: out %ax, $0x10 # 像 0x10 的端口輸出 ax 的內(nèi)容,at&t匯編的操作數(shù)和Intel的相反。 inc %ax # ax 值加一 jmp loop1 # 繼續(xù)循環(huán)
這個(gè)匯編的作用就是一直不停的向0x10端口輸出一字節(jié)的值。
從main函數(shù)開(kāi)始說(shuō)起
int main(int argc, char **argv) { int ret = 0; // 初始化kvm結(jié)構(gòu)體 struct kvm *kvm = kvm_init(); if (kvm == NULL) { fprintf(stderr, "kvm init fauilt\n"); return -1; } // 創(chuàng)建VM,并分配內(nèi)存空間 if (kvm_create_vm(kvm, RAM_SIZE) < 0) { fprintf(stderr, "create vm fault\n"); return -1; } // 加載鏡像 load_binary(kvm); // only support one vcpu now kvm->vcpu_number = 1; // 創(chuàng)建執(zhí)行現(xiàn)場(chǎng) kvm->vcpus = kvm_init_vcpu(kvm, 0, kvm_cpu_thread); // 啟動(dòng)虛擬機(jī) kvm_run_vm(kvm); kvm_clean_vm(kvm); kvm_clean_vcpu(kvm->vcpus); kvm_clean(kvm); }
第一步,調(diào)用kvm_init() 初始化了 kvm 結(jié)構(gòu)體。先來(lái)看看怎么定義一個(gè)簡(jiǎn)單的kvm。
struct kvm { int dev_fd; // /dev/kvm 的句柄 int vm_fd; // GUEST 的句柄 __u64 ram_size; // GUEST 的內(nèi)存大小 __u64 ram_start; // GUEST 的內(nèi)存起始地址, // 這個(gè)地址是qemu emulator通過(guò)mmap映射的地址 int kvm_version; struct kvm_userspace_memory_region mem; // slot 內(nèi)存結(jié)構(gòu),由用戶空間填充、 // 允許對(duì)guest的地址做分段。將多個(gè)slot組成線性地址 struct vcpu *vcpus; // vcpu 數(shù)組 int vcpu_number; // vcpu 個(gè)數(shù) };
初始化 kvm 結(jié)構(gòu)體。
struct kvm *kvm_init(void) { struct kvm *kvm = malloc(sizeof(struct kvm)); kvm->dev_fd = open(KVM_DEVICE, O_RDWR); // 打開(kāi) /dev/kvm 獲取 kvm 句柄 if (kvm->dev_fd < 0) { perror("open kvm device fault: "); return NULL; } kvm->kvm_version = ioctl(kvm->dev_fd, KVM_GET_API_VERSION, 0); // 獲取 kvm API 版本 return kvm; }
第二步+第三步,創(chuàng)建虛擬機(jī),獲取到虛擬機(jī)句柄,并為其分配內(nèi)存。
int kvm_create_vm(struct kvm *kvm, int ram_size) { int ret = 0; // 調(diào)用 KVM_CREATE_KVM 接口獲取 vm 句柄 kvm->vm_fd = ioctl(kvm->dev_fd, KVM_CREATE_VM, 0); if (kvm->vm_fd < 0) { perror("can not create vm"); return -1; } // 為 kvm 分配內(nèi)存。通過(guò)系統(tǒng)調(diào)用. kvm->ram_size = ram_size; kvm->ram_start = (__u64)mmap(NULL, kvm->ram_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); if ((void *)kvm->ram_start == MAP_FAILED) { perror("can not mmap ram"); return -1; } // kvm->mem 結(jié)構(gòu)需要初始化后傳遞給 KVM_SET_USER_MEMORY_REGION 接口 // 只有一個(gè)內(nèi)存槽 kvm->mem.slot = 0; // guest 物理內(nèi)存起始地址 kvm->mem.guest_phys_addr = 0; // 虛擬機(jī)內(nèi)存大小 kvm->mem.memory_size = kvm->ram_size; // 虛擬機(jī)內(nèi)存在host上的用戶空間地址,這里就是綁定內(nèi)存給guest kvm->mem.userspace_addr = kvm->ram_start; // 調(diào)用 KVM_SET_USER_MEMORY_REGION 為虛擬機(jī)分配內(nèi)存。 ret = ioctl(kvm->vm_fd, KVM_SET_USER_MEMORY_REGION, &(kvm->mem)); if (ret < 0) { perror("can not set user memory region"); return ret; } return ret; }
接下來(lái)就是load_binary把二進(jìn)制文件load到虛擬機(jī)的內(nèi)存中來(lái),在第一個(gè)demo中我們是直接把字節(jié)碼放到了內(nèi)存中,這里模擬鏡像加載步驟,把二進(jìn)制文件加載到內(nèi)存中。
void load_binary(struct kvm *kvm) { int fd = open(BINARY_FILE, O_RDONLY); // 打開(kāi)這個(gè)二進(jìn)制文件(鏡像) if (fd < 0) { fprintf(stderr, "can not open binary file\n"); exit(1); } int ret = 0; char *p = (char *)kvm->ram_start; while(1) { ret = read(fd, p, 4096); // 將鏡像內(nèi)容加載到虛擬機(jī)的內(nèi)存中 if (ret <= 0) { break; } printf("read size: %d", ret); p += ret; } }
加載完鏡像后,需要初始化vCPU,以便能夠運(yùn)行鏡像內(nèi)容
struct vcpu { int vcpu_id; // vCPU id,vCPU int vcpu_fd; // vCPU 句柄 pthread_t vcpu_thread; // vCPU 線程句柄 struct kvm_run *kvm_run; // KVM 運(yùn)行時(shí)結(jié)構(gòu),也可以看做是上下文 int kvm_run_mmap_size; // 運(yùn)行時(shí)結(jié)構(gòu)大小 struct kvm_regs regs; // vCPU的寄存器 struct kvm_sregs sregs; // vCPU的特殊寄存器 void *(*vcpu_thread_func)(void *); // 線程執(zhí)行函數(shù) }; struct vcpu *kvm_init_vcpu(struct kvm *kvm, int vcpu_id, void *(*fn)(void *)) { // 申請(qǐng)vcpu結(jié)構(gòu) struct vcpu *vcpu = malloc(sizeof(struct vcpu)); // 只有一個(gè) vCPU,所以這里只初始化一個(gè) vcpu->vcpu_id = 0; // 調(diào)用 KVM_CREATE_VCPU 獲取 vCPU 句柄,并關(guān)聯(lián)到kvm->vm_fd(由KVM_CREATE_VM返回) vcpu->vcpu_fd = ioctl(kvm->vm_fd, KVM_CREATE_VCPU, vcpu->vcpu_id); if (vcpu->vcpu_fd < 0) { perror("can not create vcpu"); return NULL; } // 獲取KVM運(yùn)行時(shí)結(jié)構(gòu)大小 vcpu->kvm_run_mmap_size = ioctl(kvm->dev_fd, KVM_GET_VCPU_MMAP_SIZE, 0); if (vcpu->kvm_run_mmap_size < 0) { perror("can not get vcpu mmsize"); return NULL; } printf("%d\n", vcpu->kvm_run_mmap_size); // 將 vcpu_fd 的內(nèi)存映射給 vcpu->kvm_run結(jié)構(gòu)。相當(dāng)于一個(gè)關(guān)聯(lián)操作 // 以便能夠在虛擬機(jī)退出的時(shí)候獲取到vCPU的返回值等信息 vcpu->kvm_run = mmap(NULL, vcpu->kvm_run_mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, vcpu->vcpu_fd, 0); if (vcpu->kvm_run == MAP_FAILED) { perror("can not mmap kvm_run"); return NULL; } // 設(shè)置線程執(zhí)行函數(shù) vcpu->vcpu_thread_func = fn; return vcpu; }
最后一步,以上工作就緒后,啟動(dòng)虛擬機(jī)。
void kvm_run_vm(struct kvm *kvm) { int i = 0; for (i = 0; i < kvm->vcpu_number; i++) { // 啟動(dòng)線程執(zhí)行 vcpu_thread_func 并將 kvm 結(jié)構(gòu)作為參數(shù)傳遞給線程 if (pthread_create(&(kvm->vcpus->vcpu_thread), (const pthread_attr_t *)NULL, kvm->vcpus[i].vcpu_thread_func, kvm) != 0) { perror("can not create kvm thread"); exit(1); } } pthread_join(kvm->vcpus->vcpu_thread, NULL); }
啟動(dòng)虛擬機(jī)其實(shí)就是創(chuàng)建線程,并執(zhí)行相應(yīng)的線程回調(diào)函數(shù)。
線程回調(diào)函數(shù)在kvm_init_vcpu的時(shí)候傳入
void *kvm_cpu_thread(void *data) { // 獲取參數(shù) struct kvm *kvm = (struct kvm *)data; int ret = 0; // 設(shè)置KVM的參數(shù) kvm_reset_vcpu(kvm->vcpus); while (1) { printf("KVM start run\n"); // 啟動(dòng)虛擬機(jī),此時(shí)的虛擬機(jī)已經(jīng)有內(nèi)存和CPU了,可以運(yùn)行起來(lái)了。 ret = ioctl(kvm->vcpus->vcpu_fd, KVM_RUN, 0); if (ret < 0) { fprintf(stderr, "KVM_RUN failed\n"); exit(1); } // 前文 kvm_init_vcpu 函數(shù)中,將 kvm_run 關(guān)聯(lián)了 vCPU 結(jié)構(gòu)的內(nèi)存 // 所以這里虛擬機(jī)退出的時(shí)候,可以獲取到 exit_reason,虛擬機(jī)退出原因 switch (kvm->vcpus->kvm_run->exit_reason) { case KVM_EXIT_UNKNOWN: printf("KVM_EXIT_UNKNOWN\n"); break; case KVM_EXIT_DEBUG: printf("KVM_EXIT_DEBUG\n"); break; // 虛擬機(jī)執(zhí)行了IO操作,虛擬機(jī)模式下的CPU會(huì)暫停虛擬機(jī)并 // 把執(zhí)行權(quán)交給emulator case KVM_EXIT_IO: printf("KVM_EXIT_IO\n"); printf("out port: %d, data: %d\n", kvm->vcpus->kvm_run->io.port, *(int *)((char *)(kvm->vcpus->kvm_run) + kvm->vcpus->kvm_run->io.data_offset) ); sleep(1); break; // 虛擬機(jī)執(zhí)行了memory map IO操作 case KVM_EXIT_MMIO: printf("KVM_EXIT_MMIO\n"); break; case KVM_EXIT_INTR: printf("KVM_EXIT_INTR\n"); break; case KVM_EXIT_SHUTDOWN: printf("KVM_EXIT_SHUTDOWN\n"); goto exit_kvm; break; default: printf("KVM PANIC\n"); goto exit_kvm; } } exit_kvm: return 0; } void kvm_reset_vcpu (struct vcpu *vcpu) { if (ioctl(vcpu->vcpu_fd, KVM_GET_SREGS, &(vcpu->sregs)) < 0) { perror("can not get sregs\n"); exit(1); } // #define CODE_START 0x1000 /* sregs 結(jié)構(gòu)體 x86 struct kvm_sregs { struct kvm_segment cs, ds, es, fs, gs, ss; struct kvm_segment tr, ldt; struct kvm_dtable gdt, idt; __u64 cr0, cr2, cr3, cr4, cr8; __u64 efer; __u64 apic_base; __u64 interrupt_bitmap[(KVM_NR_INTERRUPTS + 63) / 64]; }; */ // cs 為code start寄存器,存放了程序的起始地址 vcpu->sregs.cs.selector = CODE_START; vcpu->sregs.cs.base = CODE_START * 16; // ss 為堆棧寄存器,存放了堆棧的起始位置 vcpu->sregs.ss.selector = CODE_START; vcpu->sregs.ss.base = CODE_START * 16; // ds 為數(shù)據(jù)段寄存器,存放了數(shù)據(jù)開(kāi)始地址 vcpu->sregs.ds.selector = CODE_START; vcpu->sregs.ds.base = CODE_START *16; // es 為附加段寄存器 vcpu->sregs.es.selector = CODE_START; vcpu->sregs.es.base = CODE_START * 16; // fs, gs 同樣為段寄存器 vcpu->sregs.fs.selector = CODE_START; vcpu->sregs.fs.base = CODE_START * 16; vcpu->sregs.gs.selector = CODE_START; // 為vCPU設(shè)置以上寄存器的值 if (ioctl(vcpu->vcpu_fd, KVM_SET_SREGS, &vcpu->sregs) < 0) { perror("can not set sregs"); exit(1); } // 設(shè)置寄存器標(biāo)志位 vcpu->regs.rflags = 0x0000000000000002ULL; // rip 表示了程序的起始指針,地址為 0x0000000 // 在加載鏡像的時(shí)候,我們直接將binary讀取到了虛擬機(jī)的內(nèi)存起始位 // 所以虛擬機(jī)開(kāi)始的時(shí)候會(huì)直接運(yùn)行binary vcpu->regs.rip = 0; // rsp 為堆棧頂 vcpu->regs.rsp = 0xffffffff; // rbp 為堆棧底部 vcpu->regs.rbp= 0; if (ioctl(vcpu->vcpu_fd, KVM_SET_REGS, &(vcpu->regs)) < 0) { perror("KVM SET REGS\n"); exit(1); } }
運(yùn)行一下結(jié)果,可以看到當(dāng)虛擬機(jī)執(zhí)行了指令 out %ax, $0x10
的時(shí)候,會(huì)引起虛擬機(jī)的退出,這是CPU虛擬化里面將要介紹的特殊機(jī)制。
宿主機(jī)獲取到虛擬機(jī)退出的原因后,獲取相應(yīng)的輸出。這里的步驟就類似于IO虛擬化,直接讀取IO模塊的內(nèi)存,并輸出結(jié)果。
? kvmsample git:(master) ? ./kvmsample read size: 712288 KVM start run KVM_EXIT_IO out port: 16, data: 0 KVM start run KVM_EXIT_IO out port: 16, data: 1 KVM start run KVM_EXIT_IO out port: 16, data: 2 KVM start run KVM_EXIT_IO out port: 16, data: 3 KVM start run KVM_EXIT_IO out port: 16, data: 4 ...
虛擬機(jī)的啟動(dòng)過(guò)程基本上可以這么總結(jié):
創(chuàng)建kvm句柄->創(chuàng)建vm->分配內(nèi)存->加載鏡像到內(nèi)存->啟動(dòng)線程執(zhí)行KVM_RUN。從這個(gè)虛擬機(jī)的demo可以看出,虛擬機(jī)的內(nèi)存是由宿主機(jī)通過(guò)mmap調(diào)用映射給虛擬機(jī)的,而vCPU是宿主機(jī)的一個(gè)線程,這個(gè)線程通過(guò)設(shè)置相應(yīng)的vCPU的寄存器指定了虛擬機(jī)的程序加載地址后,開(kāi)始運(yùn)行虛擬機(jī)的指令,當(dāng)虛擬機(jī)執(zhí)行了IO操作后,CPU捕獲到中斷并把執(zhí)行權(quán)又交回給宿主機(jī)。
當(dāng)然真實(shí)的qemu-kvm比這個(gè)復(fù)雜的多,包括設(shè)置很多IO設(shè)備的MMIO,設(shè)置信號(hào)處理等。
上述就是小編為大家分享的虛擬化原理及QEMU啟動(dòng)過(guò)程是怎樣的了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。