之前在 文章 中描述了 7个ioctl,现在着重分析 其中3个,这3个与虚拟机内存相关
KVM_CREATE_VM
KVM_GET_VCPU_MMAP_SIZE
KVM_SET_USER_MEMORY_REGION
- 文章 中描述了 ARMv7的虚拟化支持中的 内存虚拟化支持 Second-stage of translation
基于 两点 对 linux-5.6 ARMv7 KVM 内存虚拟化 实现的探究
猜想 : 实现了 Second-stage of translation
正常访问流程
GVA->GPA 利用 MMU 访问 guest OS 中的 页表
GPA->HPA 利用 Second-stage of translation 机制 访问 hostOS中的专门为虚拟机做的 页表
猜想正确
在 guest entry 的过程中 // 进入和退出的过程都在 __kvm_vcpu_run_nvhe 中
__kvm_vcpu_run_nvhe 调用了 __activate_vm
在 entery exit 的过程中
__kvm_vcpu_run_nvhe 调用了 __deactivate_vm
__activate_vm
write_sysreg(kvm_get_vttbr(kvm), VTTBR); // Second-stage of translation 相关的寄存器 : VTTBR
write_sysreg(vcpu->arch.midr, VPIDR);
__deactivate_vm
write_sysreg(0, VTTBR);
write_sysreg(read_sysreg(MIDR), VPIDR);
参考 https://blog.csdn.net/u011011827/article/details/120471549
当 虚拟机 添加内存的时候 , 会有 GPA 与 HVA的对应关系
但是 实际访存时, 会由 MMU 访问 VTTBR 完成 GPA(IPA) -> HPA(PA) 的转换
所以,按道理, 虚拟机添加内存的时候 , 应该会改变 VTTBR 对应的 页表
而 VTTBR 是 host linux 管理的, 所以
虚拟机进程添加内存的时候,会建立 IPA(GPA) 与 HVA 的对应关系 memslot
当 guest 运行异常时 陷入 PL2后转入PL1调用user_mem_fault(解析memslot)建立 IPA 与 GPA 的对应关系(页表)
页表是以 fault_ipa 为 源 做出了 pte 的地址
以 pfn 为 源 做出了 pte 的内容
ARMv7内存虚拟化代码关键点详解
页表基址寄存器
根据 文章 中的 “内存访存流程伪代码” 我们可以看到 需要关注几个寄存器
host 中的
HTTBR
TTBR0/TTBR1
VTTBR
guest 中的
TTBR0/TTBR1
- host 中的 HTTBR
kvm_dev_ioctl_create_vm
kvm_create_vm
hardware_enable_all
hardware_enable_nolock
kvm_arch_hardware_enable
_kvm_arch_hardware_enable
cpu_hyp_reinit
cpu_hyp_reset
__hyp_reset_vectors
mov r0, #HVC_RESET_VECTORS
__HVC(0)
__kvm_call_hyp
hvc #0 // 进入 __kvm_hyp_init
bx lr
ret lr
__kvm_hyp_init //
@ Hyp-mode exception vector
W(b) __do_hyp_init
__do_hyp_init
// 将 r1 中的值 填充到 协处理器寄存器
@ Set HVBAR to point to the HYP vectors
mcr p15, 4, r1, c12, c0, 0 @ HVBAR
// 将 r2 r3 中的值 填充到 协处理器寄存器
@ Set the HTTBR to point to the hypervisor PGD pointer passed
mcrr p15, 4, rr_lo_hi(r2, r3), c2
- host 中的 VTTBR
KVM_CREATE_VM
kvm_dev_ioctl_create_vm
kvm_dev_ioctl_create_vm
kvm_create_vm
kvm_arch_init_vm
kvm_alloc_stage2_pgd
pgd_t *pgd;
pgd = alloc_pages_exact(stage2_pgd_size(kvm), GFP_KERNEL | __GFP_ZERO);
kvm->arch.pgd = pgd;
kvm->arch.pgd_phys = pgd_phys;
KVM_RUN
...
__kvm_vcpu_run_nvhe
__activate_vm
write_sysreg(kvm_get_vttbr(kvm), VTTBR);
baddr = kvm->arch.pgd_phys;
vmid_field = (u64)vmid->vmid << VTTBR_VMID_SHIFT;
return kvm_phys_to_vttbr(baddr) | vmid_field;
guest 访存流程 (时间角度)
想象中是这样子的流程
1. guest 内存相关 的初始化 (HTTBR 与 VTTBR 的设置) // 参考 "host 中的 HTTBR" 和 "host 中的 VTTBR"
2. guest os 开始运行
3. guest os 读 A 地址
4. 陷入 host PL2 异常 // host os 开始运行
4.1 异常退出
4.2 做相应的动作
4.3 注入中断
4.4 guest enter // guest os 开始 运行
5. guest os 陷入 guest PL1 异常
6. guest PL1 异常处理
6.1 保存现场
6.2 做相应的动作
6.2 恢复现场,恢复 pc 为访存时的pc
7. guest os 读 A 地址 // 和 3 是同一个动作
8. guest os 获取 A 地址的值
注意 : 4.2 和 6.2 是 我们关注的重点
- 初始化
arm_init
kvm_init
init_subsystems
_kvm_arch_hardware_enable
cpu_hyp_reinit
cpu_hyp_reset
__hyp_reset_vectors
mov r0, #HVC_RESET_VECTORS
__HVC(0)
__kvm_call_hyp
hvc #0
bx lr
ret lr
cpu_init_hyp_mode
__cpu_init_hyp_mode(pgd_ptr, hyp_stack_ptr, vector_ptr); // 设置最终的异常向量表
__cpu_init_stage2
kvm_call_hyp(__init_stage2_translation);
--- 进入 hyp mode 的异常向量表
__kvm_hyp_vector_ic_inv:W(add) sp, sp, #1 /* HVC 2 */
decode_vectors
blx lr @ Call the HYP function
__init_stage2_translation