基于内核的虚拟机(KVM)是一种内建于Linux的开源虚拟化技术。具体而言,KVM可帮助您将Linux转变为虚拟机监控程序,使主机计算机能够运行多个隔离的虚拟环境,即虚拟客户机或虚拟机(VM)。

Linux自内核版本号2.6.20起,植入KVM内核源码,因此KVM能享受每一项新的Linux功能、修复和发展,无需进行额外工程。

KVM全称为Kernel-based Virtual Machine,基于内核的虚拟机,它主要起到内核层的虚拟资源分配、管理,然后由用户层软件模拟出我们使用的虚拟机客户端系统。常见的应用软件如Qemu、VMware等,它们提供自己的虚拟资源管理器,然后由对应的模拟(仿真)软件显示对应的虚拟系统。

每个虚拟资源(虚拟机)可以表示为虚拟用户,它可以理解为一个由kvm设备创建的kvm结构对象,kvm通过为每个对象分配指定数量的虚拟cpu用于表示虚拟机中的cpu数量、线程数量,kvm内存管理单元分配的页做为内存使用等等。每个虚拟cpu关联到相应的物理cpu,并且虚拟cpu可以设置自己的时钟频率等等。

kvm通过获取影子(虚拟)物理位,模拟出专用的寄存器(msr 模拟器模式特定寄存器),用于一些虚拟相关的特征操作,如虚拟支持mmio的情况下,用户同样具有访问内核缓存的能力,如L1缓存等等。

kvm还具有页回收、用户统计等功能,如页从最老(最先分配)的开始检查,符合条件进行回收,统计由debug文件系统完成(stat_fops_per_vm结构对象),用户挂载、用户恢复等等。

kvm操作结构同样具有属性组的概念,它包含相应的属性文件,用于数据的读取、写入等等。

从逻辑角度来看,kvm相当于虚拟出了linux内核绝大部分重要功能,并且通过隔离手段,隔离每个用户,使其(看起来)都在自己的空间内运作。


目录


1. 函数分析

1.1 kvm_init

2. 源码结构

3. 部分结构定义

4. 扩展函数/变量


1. 函数分析

1.1 kvm_init

  kvm初始化

  kvm针对不同的架构/厂商进行初始化
  分配irqfd_cleanup_wq工作队列
  分配对应的cpu掩码
  kvm相关架构/厂商硬件设置
  检查每个cpu兼容情况
  设置热插拔状态回调函数,当前不执行回调函数(不尝试执行)
  注册kvm重启通知,系统重启时,挂起/关闭所有cpu上的VMX
  分配虚拟cpu缓存,分配cpu掩码cpu_kick_mask
  分配async_pf_cache缓存
  注册kvm设备
  注册kvm系统核心操作结构
  创建kvm_debugfs_dir及相关文件
  注册kvm_vfio_ops

int kvm_init(void *opaque, unsigned vcpu_size, unsigned vcpu_align,
                  struct module *module)
{
        struct kvm_cpu_compat_check c;
        int r;
        int cpu;

        r = kvm_arch_init(opaque); // kvm针对不同的架构/厂商进行初始化

		/*
		 * kvm_arch_init确保支持多种实现的架构(如x86上的intel和amd)最多有一个调用方
		 * 必须在kvm_irqfd_init之前调用kvm_arch/init,以避免在kvm已经为另一个实现设置时产生冲突
		 * /
		r = kvm_irqfd_init(); // 分配irqfd_cleanup_wq工作队列
		// 创建一个主机范围的工作队列,用于发出从所有vm*实例聚合的延迟关闭请求
		// 我们需要自己的隔离队列,以便在VM退出时轻松刷新工作项

		if (!zalloc_cpumask_var(&cpus_hardware_enabled, GFP_KERNEL)) { // 分配对应的cpu掩码
			r = -ENOMEM;
			goto out_free_0;
		}

		r = kvm_arch_hardware_setup(opaque); // kvm相关架构/厂商硬件设置

kvm_arch_init
kvm_arch_hardware_setup

	c.ret = &r;
	c.opaque = opaque;
	for_each_online_cpu(cpu) {
		smp_call_function_single(cpu, check_processor_compat, &c, 1); // 检查每个cpu兼容情况
		if (r < 0)
			goto out_free_2;
	}

	r = cpuhp_setup_state_nocalls(CPUHP_AP_KVM_STARTING, "kvm/cpu:starting",
				      kvm_starting_cpu, kvm_dying_cpu); // 设置热插拔状态回调函数,当前不执行回调函数(不尝试执行)

	register_reboot_notifier(&kvm_reboot_notifier); // 注册kvm重启通知
	// 系统重启时,挂机/关闭所有cpu上的VMX(Virtual Machine Extension,虚拟机扩展)

	/* kmem缓存使我们能够满足fx_save的对齐要求 */
	if (!vcpu_align)
		vcpu_align = __alignof__(struct kvm_vcpu);
	kvm_vcpu_cache =
		kmem_cache_create_usercopy("kvm_vcpu", vcpu_size, vcpu_align,
					   SLAB_ACCOUNT,
					   offsetof(struct kvm_vcpu, arch),
					   offsetofend(struct kvm_vcpu, stats_id)
					   - offsetof(struct kvm_vcpu, arch),
					   NULL); // 分配虚拟cpu缓存

	for_each_possible_cpu(cpu) {
		if (!alloc_cpumask_var_node(&per_cpu(cpu_kick_mask, cpu),
					    GFP_KERNEL, cpu_to_node(cpu))) { // cpu_kick_mask,释放使用
			r = -ENOMEM;
			goto out_free_4;
		}
	}

	r = kvm_async_pf_init(); // 分配async_pf_cache缓存

	kvm_chardev_ops.owner = module; // kvm字符设备所属者

	r = misc_register(&kvm_dev); // 注册kvm设备
	// 通过操作结构与应用软件通讯
	// 如创建或模拟虚拟用户(由应用软件模拟出的虚拟机系统)需要的一些数据

	register_syscore_ops(&kvm_syscore_ops); // 注册kvm系统核心操作结构

kvm_dev
kvm_syscore_ops

	kvm_preempt_ops.sched_in = kvm_sched_in; // 函数功能(调度加载虚拟cpu)
	kvm_preempt_ops.sched_out = kvm_sched_out; // 函数功能(变更为未运行状态)

	kvm_init_debug(); // 创建kvm_debugfs_dir及相关文件

	r = kvm_vfio_ops_init(); // 注册kvm_vfio_ops
	// kvm设备、私有数据、属性组等操作
	
	WARN_ON(r);

	return 0;
}

kvm_vfio_ops
stat_fops_per_vm


2. 源码结构

  mmu_shrinker 用于查看使用页数及回收页

static struct shrinker mmu_shrinker = {
	.count_objects = mmu_shrink_count, // 返回统计的用户使用的页数
	.scan_objects = mmu_shrink_scan, // 回收页,从最老的页开始检查
	.seeks = DEFAULT_SEEKS * 10, // 查找索引
	// #define DEFAULT_SEEKS 2
};

  kvm_dev kvm设备

static struct miscdevice kvm_dev = {
	KVM_MINOR,
	"kvm",
	&kvm_chardev_ops, // 设备操作
};

kvm_chardev_ops

  kvm_chardev_ops kvm字符设备操作

static struct file_operations kvm_chardev_ops = {
	.unlocked_ioctl = kvm_dev_ioctl,  // ioctl操作,如创建用户(虚拟资源,由应用软件模拟出虚拟机)
	// 默认case执行 kvm_arch_dev_ioctl
	
	.llseek		= noop_llseek, // 文件偏移(位置)
	KVM_COMPAT(kvm_dev_ioctl),
};

  kvm_syscore_ops kvm系统核心操作,如挂起或恢复

static struct syscore_ops kvm_syscore_ops = {
	.suspend = kvm_suspend, // 挂起
	.resume = kvm_resume, // 恢复
};

  kvm_vfio_ops vfio操作

static struct kvm_device_ops kvm_vfio_ops = {
	.name = "kvm-vfio",
	.create = kvm_vfio_create, // 创建kvm设备私有数据
	// kvm_vfio kv 
	// 初始化私有数据组链表
	
	.destroy = kvm_vfio_destroy, // 释放私有结构及kvm设备
	.set_attr = kvm_vfio_set_attr, // 设置属性组相关
	.has_attr = kvm_vfio_has_attr, // 检查属性组(是否存在某一种类型)
};

  kvm_guest_cbs kvm用户相关

static struct perf_guest_info_callbacks kvm_guest_cbs = {
	.state			= kvm_guest_state, // 获取用户状态
	.get_ip			= kvm_guest_get_ip, // 获取用户rip
	// VCPU_REGS_RIP
	
	.handle_intel_pt_intr	= NULL, // kvm_register_perf_callbacks传入函数
};

  stat_fops_per_vm kvm用户统计操作

static const struct file_operations stat_fops_per_vm = {
        .owner = THIS_MODULE,
        .open = kvm_stat_data_open, // 打开debug inode
        .release = kvm_debugfs_release, // 释放debug inode,包括属性等等
        .read = simple_attr_read, // inode读取,由inode的私有结构->attr(属性文件)
        // 从由get函数填充的缓冲区中读取
        .write = simple_attr_write, // inode写入,由inode的私有结构->attr(属性文件)
         // 将缓冲区解释为调用set函数的数字
        .llseek = no_llseek, // 文件偏移
};

3. 部分结构定义

  kvm_x86_ops kvm基于x86系列架构的操作结构

struct kvm_x86_ops {
	const char *name; // 操作结构名称

	int (*hardware_enable)(void); // 硬件支持(启动)函数
	void (*hardware_disable)(void); // 硬件禁止(停用)函数
	void (*hardware_unsetup)(void); // 硬件取消设置函数
	bool (*has_emulated_msr)(struct kvm *kvm, u32 index); // 检查模拟器编号函数
	// msr  模拟器模式特定寄存器
	 
	void (*vcpu_after_set_cpuid)(struct kvm_vcpu *vcpu); // 虚拟cpu设置cpuid函数

	unsigned int vm_size; // 虚拟机大小
	int (*vm_init)(struct kvm *kvm); // 虚拟机初始化函数
	// 用于检测及标志设置
	
	void (*vm_destroy)(struct kvm *kvm); // 虚拟机释放
	// 释放虚拟机(页)

	/* 创建,但不附加该VCPU */
	int (*vcpu_precreate)(struct kvm *kvm); // 分配中断描述符(PID)表
	// Posted Interrupt Descriptor (PID) table
	
	int (*vcpu_create)(struct kvm_vcpu *vcpu); // 创建虚拟cpu及相关的表
	
	void (*vcpu_free)(struct kvm_vcpu *vcpu); // 释放虚拟cpu
	void (*vcpu_reset)(struct kvm_vcpu *vcpu, bool init_event); // 重置(重新加载)

	void (*prepare_switch_to_guest)(struct kvm_vcpu *vcpu); // 准备转换到客户机(虚拟机用户层)
	void (*vcpu_load)(struct kvm_vcpu *vcpu, int cpu); // 切换到指定的虚拟cpu,直到找到匹配的vcpu_put
	void (*vcpu_put)(struct kvm_vcpu *vcpu); // 将虚拟CPU放在pCPU(per_cpu,获取到当前cpu)的需要唤醒的虚拟CPU列表中
	// 并在PI描述符中将WAKEUP设置为通知向量
	// 虚拟CPU被抢占时设置SN

	void (*update_exception_bitmap)(struct kvm_vcpu *vcpu); // 更新异常位图,部分情况下允许客户机访问
	int (*get_msr)(struct kvm_vcpu *vcpu, struct msr_data *msr); // 将msr值(属于 msr_info->index读入 msr_info->data
	// msr  模拟器模式特定寄存器
	
	int (*set_msr)(struct kvm_vcpu *vcpu, struct msr_data *msr); // 将msr值写入适当的寄存器(msr)
	u64 (*get_segment_base)(struct kvm_vcpu *vcpu, int seg); // 获取基本(可用)段或客户机可用段
	// 某些用户空间不保留不可用的属性
	// 由于根据VMX规范,可用段必须存在
	// 所以我们可以使用present属性修改用户空间错误,使不可用段始终不存在
	
	void (*get_segment)(struct kvm_vcpu *vcpu,
			    struct kvm_segment *var, int seg);  // 获取可用段
	int (*get_cpl)(struct kvm_vcpu *vcpu); // 如果vm86_active不为真,读取段缓存中的ar值
	void (*set_segment)(struct kvm_vcpu *vcpu,
			    struct kvm_segment *var, int seg); // 更新(写入到)kvm段结构
	void (*get_cs_db_l_bits)(struct kvm_vcpu *vcpu, int *db, int *l);
	void (*set_cr0)(struct kvm_vcpu *vcpu, unsigned long cr0);
	void (*post_set_cr3)(struct kvm_vcpu *vcpu, unsigned long cr3);
	bool (*is_valid_cr4)(struct kvm_vcpu *vcpu, unsigned long cr0);
	void (*set_cr4)(struct kvm_vcpu *vcpu, unsigned long cr4);
	int (*set_efer)(struct kvm_vcpu *vcpu, u64 efer);
	void (*get_idt)(struct kvm_vcpu *vcpu, struct desc_ptr *dt);
	void (*set_idt)(struct kvm_vcpu *vcpu, struct desc_ptr *dt);
	void (*get_gdt)(struct kvm_vcpu *vcpu, struct desc_ptr *dt);
	void (*set_gdt)(struct kvm_vcpu *vcpu, struct desc_ptr *dt);
	void (*sync_dirty_debug_regs)(struct kvm_vcpu *vcpu);
	void (*set_dr7)(struct kvm_vcpu *vcpu, unsigned long value);
	void (*cache_reg)(struct kvm_vcpu *vcpu, enum kvm_reg reg);
	unsigned long (*get_rflags)(struct kvm_vcpu *vcpu);
	void (*set_rflags)(struct kvm_vcpu *vcpu, unsigned long rflags);
	bool (*get_if_flag)(struct kvm_vcpu *vcpu);

	void (*flush_tlb_all)(struct kvm_vcpu *vcpu);
	void (*flush_tlb_current)(struct kvm_vcpu *vcpu);
	int  (*tlb_remote_flush)(struct kvm *kvm);
	int  (*tlb_remote_flush_with_range)(struct kvm *kvm,
			struct kvm_tlb_range *range);

	/*
	 * 刷新与给定GVA关联的任何TLB条目
	 * 不需要刷新GPA->HPA映射
	 * 可能会通过INVLPG获取非规范地址,如果适当的话,实现可能会选择忽略这些地址
	 */
	void (*flush_tlb_gva)(struct kvm_vcpu *vcpu, gva_t addr);

	/*
	 * 刷新客户机创建的所有TLB条目
	 * 与tlb_flush_gva()一样,不需要刷新GPA->HPA映射
	 */
	void (*flush_tlb_guest)(struct kvm_vcpu *vcpu);

	int (*vcpu_pre_run)(struct kvm_vcpu *vcpu);
	enum exit_fastpath_completion (*vcpu_run)(struct kvm_vcpu *vcpu);
	int (*handle_exit)(struct kvm_vcpu *vcpu,
		enum exit_fastpath_completion exit_fastpath);
	int (*skip_emulated_instruction)(struct kvm_vcpu *vcpu);
	void (*update_emulated_instruction)(struct kvm_vcpu *vcpu);
	void (*set_interrupt_shadow)(struct kvm_vcpu *vcpu, int mask);
	u32 (*get_interrupt_shadow)(struct kvm_vcpu *vcpu);
	void (*patch_hypercall)(struct kvm_vcpu *vcpu,
				unsigned char *hypercall_addr);
	void (*inject_irq)(struct kvm_vcpu *vcpu, bool reinjected);
	void (*inject_nmi)(struct kvm_vcpu *vcpu);
	void (*inject_exception)(struct kvm_vcpu *vcpu);
	void (*cancel_injection)(struct kvm_vcpu *vcpu);
	int (*interrupt_allowed)(struct kvm_vcpu *vcpu, bool for_injection);
	int (*nmi_allowed)(struct kvm_vcpu *vcpu, bool for_injection);
	bool (*get_nmi_mask)(struct kvm_vcpu *vcpu);
	void (*set_nmi_mask)(struct kvm_vcpu *vcpu, bool masked);
	void (*enable_nmi_window)(struct kvm_vcpu *vcpu);
	void (*enable_irq_window)(struct kvm_vcpu *vcpu);
	void (*update_cr8_intercept)(struct kvm_vcpu *vcpu, int tpr, int irr);
	bool (*check_apicv_inhibit_reasons)(enum kvm_apicv_inhibit reason);
	void (*refresh_apicv_exec_ctrl)(struct kvm_vcpu *vcpu);
	void (*hwapic_irr_update)(struct kvm_vcpu *vcpu, int max_irr);
	void (*hwapic_isr_update)(int isr);
	bool (*guest_apic_has_interrupt)(struct kvm_vcpu *vcpu);
	void (*load_eoi_exitmap)(struct kvm_vcpu *vcpu, u64 *eoi_exit_bitmap);
	void (*set_virtual_apic_mode)(struct kvm_vcpu *vcpu);
	void (*set_apic_access_page_addr)(struct kvm_vcpu *vcpu);
	void (*deliver_interrupt)(struct kvm_lapic *apic, int delivery_mode,
				  int trig_mode, int vector);
	int (*sync_pir_to_irr)(struct kvm_vcpu *vcpu);
	int (*set_tss_addr)(struct kvm *kvm, unsigned int addr);
	int (*set_identity_map_addr)(struct kvm *kvm, u64 ident_addr);
	u8 (*get_mt_mask)(struct kvm_vcpu *vcpu, gfn_t gfn, bool is_mmio);

	void (*load_mmu_pgd)(struct kvm_vcpu *vcpu, hpa_t root_hpa,
			     int root_level);

	bool (*has_wbinvd_exit)(void);

	u64 (*get_l2_tsc_offset)(struct kvm_vcpu *vcpu);
	u64 (*get_l2_tsc_multiplier)(struct kvm_vcpu *vcpu);
	void (*write_tsc_offset)(struct kvm_vcpu *vcpu, u64 offset);
	void (*write_tsc_multiplier)(struct kvm_vcpu *vcpu, u64 multiplier);

	/*
	 * 检索一些任意的退出信息
	 * 仅在跟踪点或错误路径内使用
	 */
	void (*get_exit_info)(struct kvm_vcpu *vcpu, u32 *reason,
			      u64 *info1, u64 *info2,
			      u32 *exit_int_info, u32 *exit_int_info_err_code);

	int (*check_intercept)(struct kvm_vcpu *vcpu,
			       struct x86_instruction_info *info,
			       enum x86_intercept_stage stage,
			       struct x86_exception *exception);
	void (*handle_exit_irqoff)(struct kvm_vcpu *vcpu);

	void (*request_immediate_exit)(struct kvm_vcpu *vcpu);

	void (*sched_in)(struct kvm_vcpu *kvm, int cpu);

	/*
	 * CPU脏日志缓冲区的大小,即VMX的PML缓冲区
	 * 零值表示不支持或禁用CPU脏日志记录
	 */
	int cpu_dirty_log_size;
	void (*update_cpu_dirty_logging)(struct kvm_vcpu *vcpu);

	const struct kvm_x86_nested_ops *nested_ops;

	void (*vcpu_blocking)(struct kvm_vcpu *vcpu);
	void (*vcpu_unblocking)(struct kvm_vcpu *vcpu);

	int (*pi_update_irte)(struct kvm *kvm, unsigned int host_irq,
			      uint32_t guest_irq, bool set);
	void (*pi_start_assignment)(struct kvm *kvm);
	void (*apicv_post_state_restore)(struct kvm_vcpu *vcpu);
	bool (*dy_apicv_has_pending_interrupt)(struct kvm_vcpu *vcpu);

	int (*set_hv_timer)(struct kvm_vcpu *vcpu, u64 guest_deadline_tsc,
			    bool *expired);
	void (*cancel_hv_timer)(struct kvm_vcpu *vcpu);

	void (*setup_mce)(struct kvm_vcpu *vcpu);

	int (*smi_allowed)(struct kvm_vcpu *vcpu, bool for_injection);
	int (*enter_smm)(struct kvm_vcpu *vcpu, char *smstate);
	int (*leave_smm)(struct kvm_vcpu *vcpu, const char *smstate);
	void (*enable_smi_window)(struct kvm_vcpu *vcpu);

	int (*mem_enc_ioctl)(struct kvm *kvm, void __user *argp);
	int (*mem_enc_register_region)(struct kvm *kvm, struct kvm_enc_region *argp);
	int (*mem_enc_unregister_region)(struct kvm *kvm, struct kvm_enc_region *argp);
	int (*vm_copy_enc_context_from)(struct kvm *kvm, unsigned int source_fd);
	int (*vm_move_enc_context_from)(struct kvm *kvm, unsigned int source_fd);
	void (*guest_memory_reclaimed)(struct kvm *kvm);

	int (*get_msr_feature)(struct kvm_msr_entry *entry);

	bool (*can_emulate_instruction)(struct kvm_vcpu *vcpu, int emul_type,
					void *insn, int insn_len);

	bool (*apic_init_signal_blocked)(struct kvm_vcpu *vcpu);
	int (*enable_direct_tlbflush)(struct kvm_vcpu *vcpu);

	void (*migrate_timers)(struct kvm_vcpu *vcpu);
	void (*msr_filter_changed)(struct kvm_vcpu *vcpu);
	int (*complete_emulated_msr)(struct kvm_vcpu *vcpu, int err);

	void (*vcpu_deliver_sipi_vector)(struct kvm_vcpu *vcpu, u8 vector);

	/*
	 * 返回vCPU特定的APICv禁止原因
	 */
	unsigned long (*vcpu_get_apicv_inhibit_reasons)(struct kvm_vcpu *vcpu);
};

  cpufreq_freqs cpu频率描述

 当cpufreq驱动程序切换cpu内核频率时,会访问/修改这个结构

struct cpufreq_freqs {
	struct cpufreq_policy *policy; // cpu频率策略
	unsigned int old; // 旧频率
	unsigned int new; // 新频率
	u8 flags;		/* cpufreq_driver标志 */
};

  kvm_caps kvm能力估算

struct kvm_caps {
	/* 是否支持对用户tsc频率的控制 */
	bool has_tsc_control;
	/* 用户端支持的最大tsc_khz */
	u32  max_guest_tsc_khz;
	/* TSC比例小数部分的位数 */
	u8   tsc_scaling_ratio_frac_bits;
	/* TSC比例的最大允许值 */
	u64  max_tsc_scaling_ratio;
	/* 1ull << kvm_caps.tsc_scaling_ratio_frac_bits */
	u64  default_tsc_scaling_ratio;
	/* 是否支持总线锁定检测 */
	bool has_bus_lock_exit;
	/* 是否支持通知VM退出 */
	bool has_notify_vmexit;

	u64 supported_mce_cap; // mce能力
	// mce 机器检查异常(Machine Check Exception)
	 
	u64 supported_xcr0; // 扩展控制寄存器
	u64 supported_xss; 
};

4. 扩展函数/变量

  kvm_arch_init kvm针对不同的架构进行初始化

  不支持某些硬件功能,比如某些旧cpu不支持虚拟化新特征,加载失败
  某些功能无法使用导致加载失败,比如MSR_VM_CR
  kvm内存管理单元根据不同的厂商指定的初始化方案
  kvm时钟速度初始化
  获取扩展控制寄存器的值
  tsc更新时钟源

int kvm_arch_init(void *opaque)
{
        struct kvm_x86_init_ops *ops = opaque;
        u64 host_pat;
        int r;

        if (kvm_x86_ops.hardware_enable) { // 已加载厂商模块
                pr_err("kvm: already loaded vendor module '%s'\n", kvm_x86_ops.name); 
                return -EEXIST;
        }

		if (!ops->cpu_has_kvm_support()) { // 不支持某些硬件功能,比如某些旧cpu不支持虚拟化新特征
			pr_err_ratelimited("kvm: no hardware support for '%s'\n",
					   ops->runtime_ops->name);
			return -EOPNOTSUPP;
		}

		if (ops->disabled_by_bios()) { // 某些功能无法使用导致加载失败,比如MSR_VM_CR
		pr_err_ratelimited("kvm: support for '%s' disabled by bios\n",
				   ops->runtime_ops->name);
		return -EOPNOTSUPP;
	}

	/* 
	 * KVM明确假设客户机具有FPU和FXSAVE/FXRSTOR
	 * 例如,KVM_GET_FPU将vCPU的FPU状态显式转换为fxregs_state结构
	 * /
	if (!boot_cpu_has(X86_FEATURE_FPU) || !boot_cpu_has(X86_FEATURE_FXSR)) {
		printk(KERN_ERR "kvm: inadequate fpu\n");
		return -EOPNOTSUPP;
	}

	/*
	 * KVM假设PAT条目“0”编码WB memtype,并简单地将SPTE中的PAT位归零
	 * 如果PAT[0]编程为WB以外的其他值,则通过
	 * 请注意,EPT不使用PAT,但不必担心例外情况
	 * PAT[0]在重置时被设置为WB,并且也被内核设置为WB,即故障表示内核错误或固件损坏
	 * /
	if (rdmsrl_safe(MSR_IA32_CR_PAT, &host_pat) ||
	    (host_pat & GENMASK(2, 0)) != 6) {
	// #define MSR_IA32_CR_PAT			0x00000277
	
		pr_err("kvm: host PAT[0] is not WB\n");
		return -EIO;
	}

	x86_emulator_cache = kvm_alloc_emulator_cache(); // 分配模拟器缓存
	// "x86_emulator" 用于向用户空间的数据拷贝

	user_return_msrs = alloc_percpu(struct kvm_user_return_msrs); // 每cpu中分配kvm_user_return_msrs结构对象

	kvm_nr_uret_msrs = 0; 

	r = kvm_mmu_vendor_module_init(); // kvm内存管理单元根据不同的厂商指定的初始化方案

kvm_x86_ops
kvm_mmu_vendor_module_init

kvm_timer_init(); // kvm时钟速度初始化

kvm_timer_init

if (boot_cpu_has(X86_FEATURE_XSAVE)) {
		host_xcr0 = xgetbv(XCR_XFEATURE_ENABLED_MASK); // 获取扩展控制寄存器的值
		// 将ECX寄存器中指定的扩展控制寄存器(XCR)的内容读取到寄存器EDX:EAX中
		kvm_caps.supported_xcr0 = host_xcr0 & KVM_SUPPORTED_XCR0;
	}

	if (pi_inject_timer == -1)
		pi_inject_timer = housekeeping_enabled(HK_TYPE_TIMER); // 隔离时间类型
#ifdef CONFIG_X86_64
	pvclock_gtod_register_notifier(&pvclock_gtod_notifier); // 注册pvclock 时间数据更新监听器

	if (hypervisor_is_type(X86_HYPER_MS_HYPERV))
		set_hv_tscchange_cb(kvm_hyperv_tsc_notifier); // tsc更新时钟源
#endif

	return 0;
}

  kvm_mmu_vendor_module_init kvm内存管理单元根据不同的厂商指定的初始化方案

  kvm内存管理单元重置为原始模块参数值,条件允许的情况下使用MMIO缓存
  创建pte_list_desc_cache缓存对象,创建mmu_page_header_cache缓存对象
  分配每cpu计数对象,用于统计所有用户,即每个用户使用的内存页数(链表形式关联)
  注册内存单元管理结构(内部通过kvm_total_used_mmu_pages对象管理)

int kvm_mmu_vendor_module_init(void)
{
	int ret = -ENOMEM;

	/*
	 * MMU角色使用联合别名,这通常是一种未定义的行为
	 * 然而,我们应该知道编译器的行为,目前的现状不太可能改变
	 * 下面的检测应该让我们知道假设是否成立
	 */
	BUILD_BUG_ON(sizeof(union kvm_mmu_page_role) != sizeof(u32)); // 如果条件为真,则中断编译
	BUILD_BUG_ON(sizeof(union kvm_mmu_extended_role) != sizeof(u32));
	BUILD_BUG_ON(sizeof(union kvm_cpu_role) != sizeof(u64));

	kvm_mmu_reset_all_pte_masks(); // kvm内存管理单元重置为原始模块参数值,条件允许的情况下使用MMIO缓存

kvm_mmu_reset_all_pte_masks

pte_list_desc_cache = kmem_cache_create("pte_list_desc",
					    sizeof(struct pte_list_desc),
					    0, SLAB_ACCOUNT, NULL); // 创建pte_list_desc_cache缓存对象

mmu_page_header_cache = kmem_cache_create("kvm_mmu_page_header",
						  sizeof(struct kvm_mmu_page),
						  0, SLAB_ACCOUNT, NULL); // 创建mmu_page_header_cache缓存对象

if (percpu_counter_init(&kvm_total_used_mmu_pages, 0, GFP_KERNEL)) // 分配每cpu计数对象,用于统计所有用户,即每个用户使用的内存页数(链表形式关联)
		goto out;

ret = register_shrinker(&mmu_shrinker, "x86-mmu"); // 注册内存单元管理结构
	if (ret)
		goto out_shrinker;

	return 0;
}

mmu_shrinker

  kvm_mmu_reset_all_pte_masks kvm内存管理单元重置为原始模块参数值,条件允许的情况下使用MMIO缓存

  获取影子(虚拟)物理位,用于虚拟化
  如果CPU具有46个或更少的物理地址位,则设置适当的掩码以防止L1TF攻击
  对于影子分页和NPT,KVM使用PAT条目“0”来编码SPTE中的WB内存类型
  在MMIO SPTE中设置一个保留的PA位,以使用PFEC生成页面错误
  重置为原始模块参数值,用户空间允许MMIO缓存的情况下,根据相应条件使用

void kvm_mmu_reset_all_pte_masks(void)
{
	u8 low_phys_bits;
	u64 mask;

	shadow_phys_bits = kvm_get_shadow_phys_bits(); // 获取影子(虚拟)物理位,用于虚拟化

kvm_get_shadow_phys_bits

	/*
	 * 如果CPU具有46个或更少的物理地址位,则设置适当的掩码以防止L1TF攻击
	 * 否则,假设CPU不易受到L1TF的攻击
	 *
	 * 一些Intel CPU使用比CPUID报告的更多的PA位来寻址L1缓存
	 * 尽可能使用L1缓存的PA宽度以实现更有效的缓解
	 * 例如,如果系统RAM与合法物理地址空间的最高有效位重叠
	 */
	shadow_nonpresent_or_rsvd_mask = 0;
	low_phys_bits = boot_cpu_data.x86_phys_bits;
	if (boot_cpu_has_bug(X86_BUG_L1TF) &&
	    !WARN_ON_ONCE(boot_cpu_data.x86_cache_bits >=
			  52 - SHADOW_NONPRESENT_OR_RSVD_MASK_LEN)) { // 如果设置了X86_BUG_L1TF 并且CPU具有46个或更少 
	//  参考early_identify_cpu函数赋值
	
		low_phys_bits = boot_cpu_data.x86_cache_bits
			- SHADOW_NONPRESENT_OR_RSVD_MASK_LEN;
		shadow_nonpresent_or_rsvd_mask =
			rsvd_bits(low_phys_bits, boot_cpu_data.x86_cache_bits - 1);
	}

	shadow_nonpresent_or_rsvd_lower_gfn_mask =
		GENMASK_ULL(low_phys_bits - 1, PAGE_SHIFT);

	...
	/*
	 * 对于影子分页和NPT,KVM使用PAT条目“0”来编码SPTE中的WB内存类型
	 * 即依赖主机MTRR来提供正确的内存类型(WB是“最弱”的内存类型)
	 */
	shadow_memtype_mask	= 0;
	shadow_acc_track_mask	= 0;
	shadow_me_mask		= 0;
	shadow_me_value		= 0;

	shadow_host_writable_mask = DEFAULT_SPTE_HOST_WRITABLE;
	shadow_mmu_writable_mask  = DEFAULT_SPTE_MMU_WRITABLE;

	/*
	 * 在MMIO SPTE中设置一个保留的PA位,以使用PFEC生成页面错误
	 * MMIO访问上的RSVD=1
	 * 64位PTE(PAE、x86-64和EPT寻呼)支持最多52位PA,即,如果CPU支持52位物理地址
	 * 则PTE中没有保留的PA位,因此必须禁用保留的PA方法
	 */
	if (shadow_phys_bits < 52)
		mask = BIT_ULL(51) | PT_PRESENT_MASK;
	else
		mask = 0;

	kvm_mmu_set_mmio_spte_mask(mask, mask, ACC_WRITE_MASK | ACC_USER_MASK); // 重置为原始模块参数值,用户空间允许MMIO缓存的情况下,根据相应条件使用
	// 更新参数本身,以便用户空间可以查看KVM是否实际使用MMIO缓存
	// 如果启用L1TF缓解时MMIO值与用于保持重新定位的GFN的位冲突,则禁用MMIO缓存
	// 由于没有已知的硬件可以触发这种情况,因此不应触发这种情况
	// 例如,需要自定义MMIO值的SME/SEV CPU不易受到L1TF的影响
	// SME 安全内存加密(Secure Memory Encryption) 
	// SEV 安全加密虚拟化(Secure Encrypted Virtualization)
}

  kvm_get_shadow_phys_bits 获取影子(虚拟)物理位,用于虚拟化

static inline u8 kvm_get_shadow_phys_bits(void)
{
	/*
	 * 当在CPU检测代码中检测到MKTME或SME时,x86_hys_bit会减少
	 * 但处理器将这些减少的位视为“keyID”,因此它们不是保留位
	 * 因此,KVM需要查看CPUID报告的物理地址位
	 */
	if (likely(boot_cpu_data.extended_cpuid_level >= 0x80000008))
		return cpuid_eax(0x80000008) & 0xff; // 调用cpuid函数,获取eax寄存器值

	/*
	 * 有VMX或SVM但没有MAXPHYADDR很奇怪
	 * 可能是具有自定义CPUID的VM
	 * 继续执行内核发现的任何功能,因为这些功能不可虚拟化(SME/SEV还需要高于0x80000008的CPUID)
	 * /
	return boot_cpu_data.x86_phys_bits;
}

  参考<<x86: perf_events内核初始化>>,里面有cpuinfo_x86结构等相关的x86框架分析内容。

  kvm_timer_init kvm时钟速度初始化

  如果速率不是恒定值,动态更改CPU的时钟速度,注册通知块(cpu频率变更)
  设置热插拔状态回调函数,在状态触发的当前cpu启动回调

static void kvm_timer_init(void)
{
	if (!boot_cpu_has(X86_FEATURE_CONSTANT_TSC)) { // 如果速率不是恒定值
	// TSC以恒定速率波动
	
		max_tsc_khz = tsc_khz;

		if (IS_ENABLED(CONFIG_CPU_FREQ)) { // 默认为y 动态更改CPU的时钟速度
			struct cpufreq_policy *policy;
			int cpu;

			cpu = get_cpu(); // 当前cpu id
			policy = cpufreq_cpu_get(cpu);
			if (policy) { // 策略存在
				if (policy->cpuinfo.max_freq)
					max_tsc_khz = policy->cpuinfo.max_freq;
				cpufreq_cpu_put(policy); // 返回cpu的策略并将其标记为忙
			}
			put_cpu();
		}
		cpufreq_register_notifier(&kvmclock_cpufreq_notifier_block,
					  CPUFREQ_TRANSITION_NOTIFIER); // 注册通知块
	}
	
	cpuhp_setup_state(CPUHP_AP_X86_KVM_CLK_ONLINE, "x86/kvm/clk:online",
			  kvmclock_cpu_online, kvmclock_cpu_down_prep); // 设置热插拔状态回调函数,在状态触发的当前cpu启动回调
}

kvmclock_cpufreq_notifier

  kvmclock_cpufreq_notifier kvm更新钟频率(cpufreq_freqs->new)到cpu_tsc_khz

static int kvmclock_cpufreq_notifier(struct notifier_block *nb, unsigned long val,
				     void *data)
{
	struct cpufreq_freqs *freq = data; // cpu频率描述对象
	int cpu;

	if (val == CPUFREQ_PRECHANGE && freq->old > freq->new)
	// #define CPUFREQ_PRECHANGE		(0)
		return 0;
	if (val == CPUFREQ_POSTCHANGE && freq->old < freq->new)
	// #define CPUFREQ_POSTCHANGE		(1)
		return 0;

	for_each_cpu(cpu, freq->policy->cpus)
		__kvmclock_cpufreq_notifier(freq, cpu); // kvm更新钟频率(cpufreq_freqs->new)到cpu_tsc_khz

	return 0;
}

cpufreq_freqs
__kvmclock_cpufreq_notifier

  __kvmclock_cpufreq_notifier kvm更新钟频率(cpufreq_freqs->new)到cpu_tsc_khz

static void __kvmclock_cpufreq_notifier(struct cpufreq_freqs *freq, int cpu)
{
	struct kvm *kvm;
	struct kvm_vcpu *vcpu;
	int send_ipi = 0;
	unsigned long i;

	/*
	 * 我们允许用户暂时使用慢时钟运行,前提是我们在之后通知他们
	 * 如果我们在之前通知他们,则允许用户使用快时钟运行
	 * 因此,时间永远不会倒退
	 *
 	 * 然而,我们有一个问题,我们不能从这个函数原子地更新给定CPU的频率
 	 * 它只是一个通知器,可以从任何CPU调用
 	 * 在任意时间点改变TSC频率需要重新计算与每个VCPU的TSC相关的局部变量
 	 * 我们必须标记这些要更新的本地变量,并确保在任何用户继续之前以新的频率进行更新
	 *
	 * 不幸的是,热插拔CPU和频率变化的结合造成了难以解决的锁定问题
	 * 这些调用发生的顺序与CPU热插拔无关,它们可以相互竞争
	 * 因此,仅在热添加期间设置per_cpu(cpu_tsc_khz) =X是未定义的
	 * 实际上,在X的计算和变量的设置之间会发生CPU频率的变化
	 * 为了防止这个问题,per_cpu tsc_khz变量的所有更新都是在中断保护的IPI中完成的
	 * 所有希望更新该值的调用方都必须等待同步IPI完成(如果调用方已经在cpu上,则这很重要)
	 * 这为变量更新建立了必要的总顺序
	 *
	 * 请注意,由于在VCPU的请求位设置之后的任何时间都可能发生用户时间更新
	 * 因此必须在请求之前设置正确的TSC值
	 * 然而,为了确保在设置和获取自旋锁之间开始在硬件虚拟化中运行的任何客户机都能进行更新
	 * 我们还必须在设置请求位后ping CPU
	 * /

	smp_call_function_single(cpu, tsc_khz_changed, freq, 1); // 在特定CPU上运行函数
	 // 更新时钟频率(cpufreq_freqs->new)到cpu_tsc_khz

	mutex_lock(&kvm_lock);
	list_for_each_entry(kvm, &vm_list, vm_list) { // kvm结构链表中遍历每个节点
		kvm_for_each_vcpu(i, vcpu, kvm) { // kvm节点关联的在线的vcpu节点
			if (vcpu->cpu != cpu) // 如果虚拟cpu节点不是当前的cpu
				continue;
			kvm_make_request(KVM_REQ_CLOCK_UPDATE, vcpu); // 设置标志位到vcpu->requests
			// #define KVM_REQ_CLOCK_UPDATE		KVM_ARCH_REQ(4)
			if (vcpu->cpu != raw_smp_processor_id())
				send_ipi = 1;
		}
	}
	mutex_unlock(&kvm_lock);

	if (freq->old < freq->new && send_ipi) {
		/*
		 * 我们提高了频率
		 * 必须使用户在以新频率运行时看不到旧的kvmclock值,否则我们会让用户看到时间倒退
		 *
		 * 如果我们更新了另一个cpu的频率(可能在来宾上下文中)
		 * 请发送一个中断,将cpu踢出用户上下文
		 * 下次输入用户上下文时,将更新kvmclock,这样用户将不会看到过时的值
		 */
		smp_call_function_single(cpu, tsc_khz_changed, freq, 1); // 在特定CPU上运行函数
	}
}

  kvm_arch_hardware_setup kvm相关架构/厂商硬件设置

  读取扩展功能寄存器确定cpu位数
  初始化性能监控单元
  对应的产品硬件设置
  更新runtime_ops、pmu_ops操作结构对象
  注册用户信息回调操作结构
  计算cr4支持的标志位
  配置符合有符号整数的tsc_khz值
  kvm初始化msr特征列表

int kvm_arch_hardware_setup(void *opaque)
{
	struct kvm_x86_init_ops *ops = opaque;
	int r;

	rdmsrl_safe(MSR_EFER, &host_efer); // 读取扩展功能寄存器,64位

	if (boot_cpu_has(X86_FEATURE_XSAVES)) // 如果保存着x86特征
		rdmsrl(MSR_IA32_XSS, host_xss); // 读x86

	kvm_init_pmu_capability(); // 初始化性能监控单元 

	r = ops->hardware_setup(); // 对应的产品硬件设置

	kvm_ops_update(ops); // 更新runtime_ops、pmu_ops操作结构对象

	kvm_register_perf_callbacks(ops->handle_intel_pt_intr); // 注册用户信息回调操作结构

kvm_guest_cbs

if (!kvm_cpu_cap_has(X86_FEATURE_XSAVES))
		kvm_caps.supported_xss = 0;

#define __kvm_cpu_cap_has(UNUSED_, f) kvm_cpu_cap_has(f)
	cr4_reserved_bits = __cr4_reserved_bits(__kvm_cpu_cap_has, UNUSED_); // cr4支持的标志位
#undef __kvm_cpu_cap_has

kvm_caps

	if (kvm_caps.has_tsc_control) {
		/*
		 * 确保用户只能配置符合有符号整数的tsc_khz值
		 * 不计算最小值,因为在所有计算机上它总是1
		 */
		u64 max = min(0x7fffffffULL,
			      __scale_tsc(kvm_caps.max_tsc_scaling_ratio, tsc_khz));
		kvm_caps.max_guest_tsc_khz = max;
	}
	kvm_caps.default_tsc_scaling_ratio = 1ULL << kvm_caps.tsc_scaling_ratio_frac_bits;
	kvm_init_msr_list(); // kvm初始化msr特征列表
	// msr 模拟器模式特定寄存器
	return 0;
}
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐