時(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ǔ)介紹:VMX Root Mode
和VMX 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
中.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 errors
在kbd_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
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()
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;}
kvm__new()
申請一個(gè)kvm對象BUILD_OPTIONS
生成所有可用的命令選項(xiàng), 然后調(diào)用parse_options()
解析參數(shù) ,然后進(jìn)行一些參數(shù)的處理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");}
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};
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ù)類似處理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í),虛擬
客戶&案例
營銷資訊
關(guān)于我們
微信公眾號
版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。