KVM虚拟化 | ARM64:(一)Arm64架构下KVM 模块 初始化流程 及 Linux6.5 源码注释
《KVM虚拟化:ARM64架构下Linux源码分析系列文章》本系列文章将对Linux在ARM64架构下KVM虚拟化的实现方法举行梳理与源码分析,主要偏重点放在ARM64架构下,KVM模块初始化、vcpu虚拟、内存虚拟、以及中断虚拟(vgic)的实现上。
在举行正式先容之前,有必要对文章引用举行提前阐明。本系列文章参考了大量的博客、文章以及书籍:
[*]Linux虚拟化 - 随笔分类 - LoyenWang - 博客园
[*]泰晓科技:RISCV虚拟化
[*]Sherlock’sblog:虚拟化
[*]albertxu216/LinuxKernel_Learning/Linux6.5源码注释
KVM虚拟化 | ARM64:(一)Arm64架构下KVM 模块 初始化流程 及 Linux6.5 源码注释
kvm作为内核模块插入内核中,来实现对CPU, 内存,中断的虚拟化, 此中IO的虚拟化由qemu负责;
本文章将基于Linux6.5内核中arm架构下kvm实现细节举行源码分析
既然KVM是以内核模块的形式插入内核的,那么我们便可以从module_init()入手, 在arch/arm64/kvm/arm.c文件涉及到了对该内核模块的申明:
/*kvm 内核模块入口*/
module_init(kvm_arm_init);
进到该初始化函数中一探究竟, kvm_arm_init函数是在arm64架构下 初始化kvm的入口函数, 他的主要功能是:
[*]
[*]完成体系结构相干的初始化;
[*]执行核心函数: kvm_init()来初始化真正的KVM模块;
[*]在KVM模块初始化后, 将kvm_arm_initialised标记为初始化成功;
以是本文的主要任务分为以下两点:
[*]ARM64架构下, 怎样举行体系结构相干的检查和初始化的;
[*]kvm_init函数是怎样将kvm模块举行真正的初始化的 (包含了各类回调函数的设置,资源分配,以及装备注册等) ;
1.1 ARM体系结构相干初始化
我们先来看一下kvm_arch_init()函数中相干的源码:
arch/arm64/kvm/arm.c/kvm_arch_init源码注释
/* 在所有 CPU 上初始化 Hyp 模式并设置内存映射,准备 KVM 的使用 */
static __init int kvm_arm_init(void)
{
int err;
bool in_hyp_mode;
/*************************************************************/
/*1. 完成体系结构相关的初始化 对应linux5.15中 kvm_arch_init */
/*************************************************************/
/*1.1 调用 is_hyp_mode_available 检测硬件是否支持 Hyp 模式*/
if (!is_hyp_mode_available()) {
kvm_info("HYP mode not available\n");
return -ENODEV;
}
/*1.2 检查内核命令行是否禁用了 KVM 功能*/
if (kvm_get_mode() == KVM_MODE_NONE) {
kvm_info("KVM disabled from command line\n");
return -ENODEV;
}
/*1.3 初始化 KVM 使用的系统寄存器表*/
err = kvm_sys_reg_table_init();
if (err) {
kvm_info("Error initializing system register tables");
return err;
}
/*1.4 检测当前内核是否运行在 Hyp 模式下
* 如果不在 Hyp 模式,后续会尝试切换到 Hyp 模式
*/
in_hyp_mode = is_kernel_in_hyp_mode();
/*1.5 检查cpu的相关功能
* cpu错误修复功能
*/
if (cpus_have_final_cap(ARM64_WORKAROUND_DEVICE_LOAD_ACQUIRE) ||
cpus_have_final_cap(ARM64_WORKAROUND_1508412))
kvm_info("Guests without required CPU erratum workarounds can deadlock system!\n" \
"Only trusted guests should be used on this system.\n");
/*1.6 设置 KVM 的中间物理地址(IPA)限制*/
err = kvm_set_ipa_limit();
if (err)
return err;
/*1.7 初始化 ARM 的可扩展矢量扩展(SVE)支持*/
err = kvm_arm_init_sve();
if (err)
return err;
/*1.8 初始化虚拟机标识符(VMID)分配器
* VMID 是 ARM 架构中用于区分不同虚拟机的标识符
*/
err = kvm_arm_vmid_alloc_init();
if (err) {
kvm_err("Failed to initialize VMID allocator.\n");
return err;
}
/*1.9 如果当前未运行在 Hyp 模式,
* 则尝试通过 init_hyp_mode 切换到 Hyp 模式
*/
if (!in_hyp_mode) {
err = init_hyp_mode();
if (err)
goto out_err;
}
/*1.10 初始化向量槽,用于管理虚拟机中断向量*/
err = kvm_init_vector_slots();
if (err) {
kvm_err("Cannot initialise vector slots\n");
goto out_hyp;
}
/*1.11 初始化其他子系统(如 I/O 管理等)*/
err = init_subsystems();
if (err)
goto out_hyp;
/*1.12 根据当前模式,进行日志记录
* a. 受保护的 nVHE 模式
* b. VHE 模式*****
* c. 普通 Hyp 模式
*/
if (is_protected_kvm_enabled()) {
kvm_info("Protected nVHE mode initialized successfully\n");
} else if (in_hyp_mode) {
kvm_info("VHE mode initialized successfully\n");
} else {
kvm_info("Hyp mode initialized successfully\n");
}
/*************************************************************/
/* 体系结构相关的初始化结束 */
/*************************************************************/
/*2. 核心操作: KVM 初始化 */
/*3. 初始化完成标记*/
}
https://i-blog.csdnimg.cn/direct/60b2e96b1b3c4b74a0afd2777b2d25c1.png#pic_center
kvm_arm_init 函数在 ARM64 架构下通过一系列步骤完成体系结构相干的初始化工作。
[*]首先是对硬件以及内核是否支持虚拟化的检测,包罗检测硬件是否支持 Hyp 模式以及内核是否支持KVM;
[*]其次是对架构功能的初始化,包罗系统寄存器表的初始化、SVE 支持和 VMID 分配器的初始化,检查 CPU 特性与错误修复,设置中心物理地点(IPA)限制,
[*]最重要的是Hyp模式的切换,以及各子系统的初始化;会在背面重点分析
[*]最后,根据运行模式记载日志,确保 KVM 在 ARM64 架构上的虚拟化情况能够稳固、可靠地运行。
https://i-blog.csdnimg.cn/direct/d9938561215f4df5b3a997fcadd8a223.png#pic_center
1.1.1 init_hyp_mode 切换到hyp模式
首先我们看一下init_hyp_mode是怎样初始化并切换到hyp模式的, 该函数核心任务是初始化 ARM64 架构下的 Hyp 模式,为 KVM 提供虚拟化支持; 相干细节如下:
[*]检查条件:确保硬件和内存资源满足 Hyp 模式的运行需求。
[*]设置内存管理:初始化页表,设置 Hyp 模式的地点空间和段映射。
[*]分配资源:为每个 CPU 设置独立的栈和 per-CPU 数据。
[*]支持受保护模式**:启用 pKVM 的内存保护和 PSCI 支持。
Linux6.5/arch/arm64/kvm/arm.c/init_hyp_mode源码注释
/* Inits Hyp-mode on all online CPUs */
static int __init init_hyp_mode(void)
{
u32 hyp_va_bits;
int cpu;
int err = -ENOMEM;
/*1. 确保受保护模式的内存资源可用,
* 检查保护模式(pKVM)的内存池是否可用
*/
if (is_protected_kvm_enabled() && !hyp_mem_base)
goto out_err;
/*2. 初始化 Hyp 页表和 Hyp 地址空间映射
* 为 Hyp 模式分配和管理虚拟地址空间
*/
err = kvm_mmu_init(&hyp_va_bits);
if (err)
goto out_err;
/*3.为每个 CPU 分配 Hyp模式下 一页大小的栈空间*/
for_each_possible_cpu(cpu) {
unsigned long stack_page;
stack_page = __get_free_page(GFP_KERNEL);
if (!stack_page) {
err = -ENOMEM;
goto out_err;
}
per_cpu(kvm_arm_hyp_stack_page, cpu) = stack_page;
}
/*4. 初始化 Per-CPU 的 Hyp 区域*/
for_each_possible_cpu(cpu) {
struct page *page;
void *page_addr;
page = alloc_pages(GFP_KERNEL, nvhe_percpu_order());
if (!page) {
err = -ENOMEM;
goto out_err;
}
page_addr = page_address(page);
memcpy(page_addr, CHOOSE_NVHE_SYM(__per_cpu_start), nvhe_percpu_size());
kvm_nvhe_sym(kvm_arm_hyp_percpu_base) = (unsigned long)page_addr;
}
/*5. 创建hyp模式下的 代码段映射*/
err = create_hyp_mappings(kvm_ksym_ref(__hyp_text_start),
kvm_ksym_ref(__hyp_text_end), PAGE_HYP_EXEC);
if (err) {
kvm_err("Cannot map world-switch code\n");
goto out_err;
}
/*6. 创建hyp模式下的 只读数据段映射
* 涉及到.hyp.rodata段 和 .rodata段
*/
err = create_hyp_mappings(kvm_ksym_ref(__hyp_rodata_start),
kvm_ksym_ref(__hyp_rodata_end), PAGE_HYP_RO);
if (err) {
kvm_err("Cannot map .hyp.rodata section\n");
goto out_err;
}
err = create_hyp_mappings(kvm_ksym_ref(__start_rodata),
kvm_ksym_ref(__end_rodata), PAGE_HYP_RO);
if (err) {
kvm_err("Cannot map rodata section\n");
goto out_err;
}
/*7. 创建hyp模式下的 bss段映射
* 涉及 .hyp.bss段 和 .bss段
*/
err = create_hyp_mappings(kvm_ksym_ref(__hyp_bss_start),
kvm_ksym_ref(__hyp_bss_end), PAGE_HYP);
if (err) {
kvm_err("Cannot map hyp bss section: %d\n", err);
goto out_err;
}
err = create_hyp_mappings(kvm_ksym_ref(__hyp_bss_end),
kvm_ksym_ref(__bss_stop), PAGE_HYP_RO);
if (err) {
kvm_err("Cannot map bss section\n");
goto out_err;
}
/*8. 这段代码的功能是为每个 CPU 创建 Hyp 栈,并通过以下方式增强安全性和可靠性:
*8.1. 分配私有虚拟地址空间:每个 CPU 拥有独立的虚拟地址范围。
*8.2. 设置保护页:未映射的保护页用于检测栈溢出。
*8.3. 保存地址信息:保存栈的物理和虚拟地址,便于后续操作。
*8.4. 逐步检查和回滚:确保初始化过程中任何错误都能安全回滚。
*/
for_each_possible_cpu(cpu) {
struct kvm_nvhe_init_params *params = per_cpu_ptr_nvhe_sym(kvm_init_params, cpu);
char *stack_page = (char *)per_cpu(kvm_arm_hyp_stack_page, cpu);
unsigned long hyp_addr;
/*8.1 分配 Hyp 栈的虚拟地址空间*/
err = hyp_alloc_private_va_range(PAGE_SIZE * 2, &hyp_addr);
if (err) {
kvm_err("Cannot allocate hyp stack guard page\n");
goto out_err;
}
/*8.2 映射栈页面并设置保护页*/
err = __create_hyp_mappings(hyp_addr + PAGE_SIZE, PAGE_SIZE,
__pa(stack_page), PAGE_HYP);
if (err) {
kvm_err("Cannot map hyp stack\n");
goto out_err;
}
/*8.3 保存物理地址和虚拟地址*/
params->stack_pa = __pa(stack_page);
params->stack_hyp_va = hyp_addr + (2 * PAGE_SIZE);
}
/*9. 创建每 CPU 的 Hyp 数据区域映射*/
for_each_possible_cpu(cpu) {
char *percpu_begin = (char *)kvm_nvhe_sym(kvm_arm_hyp_percpu_base);
char *percpu_end = percpu_begin + nvhe_percpu_size();
err = create_hyp_mappings(percpu_begin, percpu_end, PAGE_HYP);
if (err) {
kvm_err("Cannot map hyp percpu region\n");
goto out_err;
}
cpu_prepare_hyp_mode(cpu, hyp_va_bits);
}
/*10. 初始化符号*/
kvm_hyp_init_symbols();
/*11. 初始化保护模式*/
if (is_protected_kvm_enabled()) {
if (IS_ENABLED(CONFIG_ARM64_PTR_AUTH_KERNEL) &&
cpus_have_const_cap(ARM64_HAS_ADDRESS_AUTH))
pkvm_hyp_init_ptrauth();
init_cpu_logical_map();
if (!init_psci_relay()) {
err = -ENODEV;
goto out_err;
}
err = kvm_hyp_init_protection(hyp_va_bits);
if (err) {
kvm_err("Failed to init hyp memory protection\n");
goto out_err;
}
}
}
我们可以看到该函数完成了PGD页表的分配, 完成了各种段的映射, 完成了异常向量表的映射; 并为每个CPU创建hyp模式下的栈等;
1.1.2 init_subsystems 初始化各个子系统
init_subsystems 函数的主要作用是初始化 KVM 在 Hyp 模式下的一些关键子系统,以确保虚拟化情况的正常运行, 这里包罗CPU低功耗通知器模块、VGIC(虚拟化中断控制器)模块、Hyp模式下的Timer模块(用于模仿虚拟化定时中断)等;
[*]
[*]初始化每个CPU的Hyp模式, 使其能够正常进入 EL2(Hypervisor Exception Level);
[*]注册 Hyp 模式的 CPU 低功耗通知器
[*]初始化 VGIC(虚拟化中断控制器)模块
[*]初始化 Hyp 模式下的 Timer
[*]注册性能监控回调,增强对性能的监控本事;
Linux6.5/arch/arm64/kvm/arm.c/init_subsystems 源码注释
https://i-blog.csdnimg.cn/direct/485f2c7a20d84fdfbfd587c25e6c4036.png#pic_center
static int __init init_subsystems(void)
{
int err = 0;
/*1. 使能每个cpu, 这样系统才能通过EL2*/
on_each_cpu(cpu_hyp_init, NULL, 1);
/*2. 注册hyp模式下 cpu的低功耗通知器*/
hyp_cpu_pm_init();
/*3.注册hpy模式下的vgic模块中断管理相关的模块*/
err = kvm_vgic_hyp_init();
switch (err) {
case 0:
vgic_present = true;
break;
case -ENODEV:
case -ENXIO:
vgic_present = false;
err = 0;
break;
default:
goto out;
}
/*4. 初始化hyp模式下的timer*/
err = kvm_timer_hyp_init(vgic_present);
if (err)
goto out;
kvm_register_perf_callbacks(NULL);
}
这里的重点在 初始化VGIC虚拟化中断控制器,以及初始化 Hyp 模式下的 Timer, 二者都是关于kvm中的虚拟中断实现;
1.2 kvm_init KVM初始化
``kvm_init` 的功能是完成 KVM 核心模块的初始化,为虚拟化情况提供运行支持。总体上,它负责建立 KVM 与用户空间的交互接口、初始化虚拟化子系统、分配必要资源,各类回调函数的设置,并确保系统在多核情况下稳固运行。
具体而言,它通过注册 CPU 热插拔回调和性能监控接口,创建 VCPU 缓存和中断管理数据结构,初始化异步页面错误机制和 VFIO 支持,实现对物理装备的虚拟化,并通过注册 /dev/kvm 字符装备为用户空间工具(如 QEMU)提供操作接口,同时具备完善的错误处置惩罚和回滚机制,保障初始化过程的可靠性。
Linux6.5/virt/kvm/kvm_main.c /kvm_init源码注释
https://i-blog.csdnimg.cn/direct/7db0d466deda4fee87fdf5b5804c18e6.png#pic_center
/*设置虚拟机环境并确保所有相关子系统的正常运行*/
int kvm_init(unsigned vcpu_size, unsigned vcpu_align, struct module *module)
{
int r;
int cpu;
/*1. 设置cpu热插拔时的回调函数*/
#ifdef CONFIG_KVM_GENERIC_HARDWARE_ENABLING
r = cpuhp_setup_state_nocalls(CPUHP_AP_KVM_ONLINE, "kvm/cpu:online",
kvm_online_cpu, kvm_offline_cpu);
if (r)
return r;
/*注册 syscore_ops,用于在系统进入休眠和恢复时处理 KVM 特定操作*/
register_syscore_ops(&kvm_syscore_ops);
#endif
/*2. 创建vcpu缓存,
* 创建一个内存缓存池 kvm_vcpu_cache,
* 用于分配 kvm_vcpu 结构.
*/
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);
if (!kvm_vcpu_cache) {
r = -ENOMEM;
goto err_vcpu_cache;
}
/*3. 为每个 CPU 分配一个 kick_mask,
* 用于管理 CPU 中断和调度相关的操作
*/
for_each_possible_cpu(cpu) {
if (!alloc_cpumask_var_node(&per_cpu(cpu_kick_mask, cpu),
GFP_KERNEL, cpu_to_node(cpu))) {
r = -ENOMEM;
goto err_cpu_kick_mask;
}
}
/*4. 创建工作队列, 用于处理VM的shutdown操作*/
r = kvm_irqfd_init();
if (r)
goto err_irqfd;
/*5. 创建用于分配kvm_async_pf的slab缓存*/
r = kvm_async_pf_init();
if (r)
goto err_async_pf;
kvm_chardev_ops.owner = module;
/*6. 设置调度切换时的回调函数*/
kvm_preempt_ops.sched_in = kvm_sched_in;
kvm_preempt_ops.sched_out = kvm_sched_out;
kvm_init_debug();
/*7. VFIO操作初始化*/
r = kvm_vfio_ops_init();
if (WARN_ON_ONCE(r))
goto err_vfio;
/*8. 注册/dev/kvm 字符设备,使用户空间可以通过该字符设备与kvm交互*/
r = misc_register(&kvm_dev);
if (r) {
pr_err("kvm: misc device register failed\n");
goto err_register;
}
...
}
[*]回调函数设置:cpuhp_setup_state_nocall与CPU的热插拔相干,register_reboot_notifer与系统的重启相干,register_syscore_ops与系统的休眠唤醒相干,而这几个模块的回调函数,终极都会去调用体系结构相干的函数去打开或关闭Hypervisor;
[*]资源分配:kmem_cache_create_usercopy与kvm_async_pf_init都是创建slab缓存,用于内核对象的分配;
[*]kvm_vfio_ops_init:VFIO是一个可以安全将装备I/O、中断、DMA导出到用户空间的框架,后续在将IO虚拟化时再深入分析;
我们把重点放到字符装备驱动注册上misc_register ,由于该模块用于用户空间与KVM内核模块举行IO交互;
1.2.1 misc_register 注册字符装备 驱动
该函数用于注册字符装备驱动, 使用户空间程序(如 QEMU)能够与 KVM 内核模块举行交互; /dev/kvm 是一个标准的字符装备文件,通过文件操作(如 ioctl)实现用户空间和内核空间之间的通讯。用户空间程序(如虚拟机管理工具)可以通过该接口向 KVM 发送指令或吸收数据。
https://i-blog.csdnimg.cn/direct/f15aa2bed4be4bf5b9cdb5d23bd2ae72.png#pic_center
该字符装备的注册, 一共涉及到了三个操作集,设置好了对应的ioctl函数,分别是字符装备操作集、kvm-vm操作集、kvm-vcpu操作集;
[*]kvm:代表kvm内核模块,可以通过kvm_dev_ioctl来管理kvm版本信息,以及vm的创建等;
[*]vm:虚拟机实例,可以通过kvm_vm_ioctl函数来创建vcpu,设置内存区间,分配中断等;
[*]vcpu:代表虚拟的CPU,可以通过kvm_vcpu_ioctl来启动或暂停CPU的运行,设置vcpu的寄存器等;
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]