kvmtool阅读笔记(一) | 通用结构体&函数执行概括

0X00 Preface

本系列笔记为学习虚拟化,KVM的读书笔记,目的为通过阅读kvmtool源码笔记并通过KVM API实现款能够运行img镜像的最小的KVM程序。

 

0X01 初始化命令

kvmtool对不懂的debug场景有不同的参数分配,因此本次只从run命令开始初次往后阅读。kvmltool的对运行命令使用结构体进行统一地初始化,代码位于kvm-cmd.c

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},
	{"list", kvm_cmd_list, kvm_list_help, 0},
	{"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},
};

设定一个运行img的命令行

sudo ./lkvm run --kernel bzImage --initrd initramfs-busybox-x86.cpio.gz

其中kernel文件bzImage以及Linux文件系统的制作可以参考https://mgalgs.io/2015/05/16/how-to-build-a-custom-linux-kernel-for-qemu-2015-edition.html,从run命令进行跟读,即kvm_cmd_run。KVM开头进行结构体初始化,整个代码如下

struct kvm {
	struct kvm_arch		arch;
	struct kvm_config	cfg;
	int			sys_fd;		/* /dev/kvm的文件句柄 */
	int			vm_fd;		/* KVM API New VM的句柄 */
	timer_t			timerid;	/* Posix timer for interrupts */

	int			nrcpus;		/* CPU的个数 */
	struct kvm_cpu		**cpus;

	u32			mem_slots;	/* for KVM_SET_USER_MEMORY_REGION */
	u64			ram_size;	/* Guest memory size, in bytes */
	void			*ram_start;
	u64			ram_pagesize;
	struct mutex		mem_banks_lock;  /* 通过线程启动CPU函数的时候作为CPU线程锁参数*/
	struct list_head	mem_banks;

	bool			nmi_disabled;
	bool			msix_needs_devid;

	const char		*vmlinux;
	struct disk_image       **disks;
	int                     nr_disks;

	int			vm_state;  /* KVM的状态:Pause/Run */

#ifdef KVM_BRLOCK_DEBUG
	pthread_rwlock_t	brlock_sem;
#endif
};

对结构体拆分分析。参数arch所代表的结构体kvm_arch存储了虚拟地址的各项参数,包括GVA(Guest Virtual address) 经过malloc之后的起始地址以及kernel和initrd系统文件加载地址参数。参数cfg所代表的结构体kvm_config则包含了许多虚拟化必备的参数,其总代码为

struct kvm_config {
	struct kvm_config_arch arch;
	struct disk_image_params disk_image[MAX_DISK_IMAGES];
	struct vfio_device_params *vfio_devices;
	u64 ram_addr;		/* Guest memory physical base address, in bytes */
	u64 ram_size;		/* Guest memory size, in bytes */
	u8 num_net_devices;
	...

    const char *kernel_cmdline;
    const char *kernel_filename;
    const char *initrd_filename;

    ...
};

本文主要条运行最小Linux必要的几个参数分析。

  • u64 ram_addr, u64 ram_size: 为启动KVM必要的内存起始位置,其存储了启动VM的起始内存地址以及内存的大小
  • const char *kernel_cmdline: 启动VM默认附加命令
  • const char *kernel_filenameconst char *initrd_filename: kernel以及initrd文件地址

struct list_head mem_banks的定义则是借用双向链表模拟VM的缺页换页的操作。 struct kvm_cpu  **cpus 顾名思义就是VM的CPU函数,使用两个指针用于模拟多核CPU,kvm_cpu结构体为

struct kvm_cpu {
	pthread_t	thread; /* CPU线程运行后的句柄 */

	unsigned long   cpu_id;

	unsigned long	riscv_xlen;
	unsigned long	riscv_isa;
	unsigned long	riscv_timebase;

	struct kvm	*kvm;
	int		vcpu_fd;
	struct kvm_run	*kvm_run;
	struct kvm_cpu_task	*task;

	u8		is_running;
	u8		paused;
	u8		needs_nmi;

	struct kvm_coalesced_mmio_ring	*ring;
};
  • struct kvm *kvm: 每一个CPU的线程都需要调用KVM的API,而KVM API调用后的结果存储在struct kvm内。因此将外部的struct kvm的指针地址也存储在cpu结构体中方便调用KVM API的内容
  • int vcpu_fd: 存储函数ioctl(vcpu->kvm->vm_fd, KVM_CREATE_VCPU, cpu_id)返回的文件句柄
  • struct kvm_run  *kvm_run:  KVM_RUN 接口的各项参数,即负责虚拟机运行的参数调整
  • struct kvm_cpu_task  *task: 用于保存正在运行的CPU任务
  • u8 is_running: 保存CPU运行的状态,同时用于结束CPU线程和运行时的各种操作 

 

0x02 KVM_CMD_RUN_INIT

kvm_cmd_run这个函数了包含了初始化并运行直到结束后退出VM的所有过程,整个函数只有三个子函数,分别是

  • kvm_cmd_run_init: 包含了初始化一个VM的所有过程,包括初始化启动命令,初始化内存,将kernel内核加载到指定的ram起始地址等
  • kvm_cmd_run_workkvm_cmd_run_exit: 使用pthread启动CPU函数,同事也包含暂停并推出一个VM,并清理运行函数的栈空间

其次,kvmtool使用了全局函数栈作为启动VM辅助方法,全局栈函数位于头文件include/kvm/util-init.h中,以后面的int型为优先级运行基础函数

#define core_init(cb) __init_list_add(cb, 0)
#define base_init(cb) __init_list_add(cb, 2)
#define dev_base_init(cb)  __init_list_add(cb, 4)
#define dev_init(cb) __init_list_add(cb, 5)
#define virtio_dev_init(cb) __init_list_add(cb, 6)
#define firmware_init(cb) __init_list_add(cb, 7)
#define late_init(cb) __init_list_add(cb, 9)

 

运行VM加载的函数顺序如图

至此为kvmtool使用的kvm结构体,以及整个VM的运行时候的大致流程图,并制作基础KVM最小运行镜像,参考项目https://github.com/christasa/trivial-kvm/tree/6bce6168baf659bc6e262194b856fb69b7b524e8

 

0XFF Reference