时间:2023-07-12 18:48:01 | 来源:网站运营
时间:2023-07-12 18:48:01 来源:网站运营
从kvmtools学习虚拟化一 基础介绍:VMX Root Mode
和VMX non-Root Mode
, 每种模式都支持ring0~ring3. 宿主机(Host)的内核和程序运行在VMX Root Mode
, 这与最普通的模式一样. 虚拟机(Guest)运行在VMX non Root Mode
. 由于VMX的支持, 现在不需要特权压缩, 虚拟机内核可以运行在VMX non Root Mode ring 0
中. 虚拟机中的程序可以运行在VMX non Root ring3
中.sudo apt-get install qemu-kvmkvm-ok
切换到kvmtools的主目录, 直接make会报错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
函数中为变量value设置一个初始值,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; ....
为了调试方便, 在Makefile
中把CFLAGS的-O2
修改为-O0
, 表示编译时不进行优化. 然后make即可CFLAGS += $(CPPFLAGS) $(DEFINES) -I$(KVM_INCLUDE) -I$(ARCH_INCLUDE) -O0 -fno-strict-aliasing -g
启虚拟机的命令如下, 其中rootfs.cpio
为编译busybox打包得到的文件镜像, bzImage
为linux内核编译之后得到镜像./lkvm run / --initrd ./rootfs.cpio / --kernel ./bzImage
main()
在去掉argv[0]之后会调用handle_command()
根据kvm_commands
中支持的命令去解析参数int main(int argc, char* argv[]){ ...; return handle_kvm_command(argc - 1, &argv[1]); //从第二个参数开始解析, 省略"lkvm"}static int handle_kvm_command(int argc, char** argv){ //进行参数解析 return handle_command(kvm_commands, argc, (const char**)&argv[0]);}
kvm_commands
是一个struct cmd_struct
组成的数组, 所有支持的命令如下struct cmd_struct { const char* cmd; //命令名字 int (*fn)(int, const char**, const char*); //执行命令 void (*help)(void); //获取命令帮助 int option; //选项};struct cmd_struct kvm_commands[] = { { "pause", kvm_cmd_pause, kvm_pause_help, 0 }, //暂停虚拟机的运行 { "resume", kvm_cmd_resume, kvm_resume_help, 0 }, //恢复虚拟机的运行 { "debug", kvm_cmd_debug, kvm_debug_help, 0 }, //从一个正在运行的虚拟机输出调试信息 { "balloon", kvm_cmd_balloon, kvm_balloon_help, 0 }, //扩张或者搜索virtio balloon { "list", kvm_cmd_list, kvm_list_help, 0 }, //输出正在host上运行的虚拟机 { "version", kvm_cmd_version, NULL, 0 }, { "--version", kvm_cmd_version, NULL, 0 }, { "stop", kvm_cmd_stop, kvm_stop_help, 0 }, //停止一个运行的虚拟机实例 { "stat", kvm_cmd_stat, kvm_stat_help, 0 }, //输出正在运行的虚拟机状态 { "help", kvm_cmd_help, NULL, 0 }, { "setup", kvm_cmd_setup, kvm_setup_help, 0 }, //设置一个新的虚拟机 { "run", kvm_cmd_run, kvm_run_help, 0 }, //启动一个虚拟机 { "sandbox", kvm_cmd_sandbox, kvm_run_help, 0 }, //在虚拟机沙盒中运行命令 { 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对应的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; ...; //根据argv[0]找到对应的struct cmd_struct p = kvm_get_command(command, argv[0]); //从&argv[1]开始作为参数, 调用命令对应的执行函数 ret = p->fn(argc - 1, &argv[1], prefix); return ret;}
我们以run命令为例子, 根据kvm_commands
可知, 接下来会进入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; //根据参数初始化一个kvm对象 kvm = kvm_cmd_run_init(argc, argv); //执行一个虚拟机 ret = kvm_cmd_run_work(kvm); //虚拟机退出 kvm_cmd_run_exit(kvm, ret); return ret;}
kvm__new()
申请一个kvm对象BUILD_OPTIONS
生成所有可用的命令选项, 然后调用parse_options()
解析参数 ,然后进行一些参数的处理init_list__init()
调用所有初始化函数来初始化此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(); //申请一个kvm对象 nr_online_cpus = sysconf(_SC_NPROCESSORS_ONLN); //获取Host中有多少CPU while (argc != 0) { BUILD_OPTIONS(options, &kvm->cfg, kvm); //初始化options为run命令的参数 //根据option解析argv参数 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个数默认为nr_online_cpus if (kvm->cfg.nrcpus == 0) kvm->cfg.nrcpus = nr_online_cpus; //没设置内存大小,就根据cpu个数计算 if (!kvm->cfg.ram_size) kvm->cfg.ram_size = get_ram_size(kvm->cfg.nrcpus); ...; //处理其余默认配置 //遍历初始化函数表, 以kvm为参数调用其中的所有函数 if (init_list__init(kvm) < 0) die("Initialisation failed");}
struct kvm* kvm__new(void){ struct kvm* kvm = calloc(1, sizeof(*kvm)); //分配内存 if (!kvm) return ERR_PTR(-ENOMEM); mutex_init(&kvm->mem_banks_lock); //初始化互斥锁 kvm->sys_fd = -1; kvm->vm_fd = -1; return kvm;}
一个struct kvm
就表示一台虚拟机, 定义如下, 其中字段比较多, 我们会在后面一点点介绍struct kvm { struct kvm_arch arch; // Guest的架构 struct kvm_config cfg; // Guest配置相关信息 int sys_fd; /* 通过"/dev/kvm"打开kvm模块的fd */ int vm_fd; /* 通过ioctl(sys_fd, KVM_CREATE_VM, ...)创建一个虚拟机时KVM返回的fd */ timer_t timerid; /* Posix timer for interrupts */ int nrcpus; /* 运行多少个CPU */ struct kvm_cpu** cpus; // CPU对象数组 u32 mem_slots; /* 内存插槽 for KVM_SET_USER_MEMORY_REGION */ u64 ram_size; // 内存大小 void* ram_start; // Host中为Guest的内存分配的地址 u64 ram_pagesize; // 内存页大小 struct mutex mem_banks_lock; struct list_head mem_banks; // Guest可能有多个内存区域, 每一个内存区域用struct kvm_mem_bank表示 bool nmi_disabled; bool msix_needs_devid; const char* vmlinux; //执行内核程序 struct disk_image** disks; //指向磁盘镜像 int nr_disks; //有多少个磁盘 int vm_state; //虚拟机状态#ifdef KVM_BRLOCK_DEBUG pthread_rwlock_t brlock_sem;#endif};
BUILD_OPTIONS
会生成一个struct option
组成的数组, 下面只以-k
为例子//name: 命令的选项数组. //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"), / ... }
其中每一个选项struct option
定义如下struct option { enum parse_opt_type type; //保存选项的类型, 最后一个参数必须设置为OPTION_END int short_name; //选项的短名, 只包含一个char, '/0'表示没有 const char* long_name; //选项的长名 void* value; //指向写入这个选项对应值的地方 const char* argh; const char* help; void* ptr; /* 选项标志的掩码: - PARSE_OPT_OPTARG: 这个选项是可选的 - PARSE_OPT_NOARG: 这个选项没有参数 - PARSE_OPT_NONEG: 这个选项不能是负的 - PARSE_OPT_HIDDEN: 展示默认用法中会跳过这个选项, 展示更长的用法时出现 */ int flags; parse_opt_cb* callback; //对于OPTION_CALLBACK, 指向需要调用的回调函数 intptr_t defval; //对于可选选项, 该值作为默认值写入(*->value)中. 对于有回调的选项则随意使用};
宏OPT_STRING
负责初始化一个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
为例子, 宏展开时会生成OPT_STRING('k', "kernel", &(&kvm->cfg)->kernel_filename, "kernel", "...")
, 在函数parse_options()
解析argv的过程中就会把内核文件名写入&(&kvm->cfg)->kernel_filename
中. 其余参数类似处理struct init_item
表示一个初始化函数, 每个初始化函数都有一个优先级, 优先级相同的初始化函数通过struct hlist_node n
组织为一个双链表, 数组init_lists
中保存各优先级函数对应的链表头.#define PRIORITY_LISTS 10static struct hlist_head init_lists[PRIORITY_LISTS]; //根据优先级排序的初始化函数//表示一个要初始化的函数struct init_item { struct hlist_node n; // 用作非循环双链表中的指针 const char* fn_name; //函数名 int (*init)(struct kvm*); //初始化函数};
kvmtool定义了如下宏, 用于注册各个优先级的初始化函数#define core_init(cb) __init_list_add(cb, 0) //核心初始化: 为Guest申请内存和CPU#define base_init(cb) __init_list_add(cb, 2) //基础初始化: 初始化CPU, 建立PCI总线#define dev_base_init(cb) __init_list_add(cb, 4) //基础设备初始化: 建立中断机制#define dev_init(cb) __init_list_add(cb, 5) //设备初始化: 建立键盘, 鼠标等具体设备//cb: 回调函数, l: 优先级, 该宏会生成一个构造函数, 在进入main之前就被调用, 从而完成init_lists的设置#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;}
函数init_list__init()
会根据优先级调用链表中所有的初始化函数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++) //从低到高遍历所有的优先级 hlist_for_each_entry(t, &init_lists[i], n) //遍历此优先级链表中所有的函数 { r = t->init(kvm); //调用该初始化函数 if (r < 0) { pr_warning("Failed init: %s/n", t->fn_name); goto fail; } }fail: return r;}
一些典型的注册的初始化函数如下, 在接下来的文章中, 我们看一下这些初始化函数到底做了什么core_init(kvm__init); //注册为核心初始化函数base_init(kvm_cpu__init);dev_base_init(pci__init);dev_base_init(irq__init);
关键词:基础,学习,虚拟