Read the fucking source code!
--By 鲁迅A picture is worth a thousand words.
--By 高尔基说明:数据结构
https://www.cnblogs.com/LoyenWang/
《Linux虚拟化KVM-Qemu分析(二)之ARMv8虚拟化》
文中描述过内存虚拟化大致框架,再来回顾一下:app
TTBR1_EL1
中,用户空间页表基地址存放在TTBR0_EL0
中;Stage
,Hypervisor
经过Stage 2
来控制虚拟机的内存视图,控制虚拟机是否能够访问某块物理内存,进而达到隔离的目的;Stage 1
:VA(Virtual Address)->IPA(Intermediate Physical Address)
,Host的操做系统控制Stage 1
的转换;Stage 2
:IPA(Intermediate Physical Address)->PA(Physical Address)
,Hypervisor控制Stage 2
的转换;猛一看上边两个图,好像明白了啥,仔细一想,啥也不明白,本文的目标就是将这个过程讲明白。框架
在开始细节讲解以前,须要先描述几个概念:函数
gva - guest virtual address gpa - guest physical address hva - host virtual address hpa - host physical address
铺垫了这么久,来到了本文的两个主题:工具
GPA->HVA
;HVA->HPA
;开始吧!this
还记得上一篇文章《Linux虚拟化KVM-Qemu分析(四)之CPU虚拟化(2)》
中的Sample Code吗?
KVM-Qemu方案中,GPA->HVA的转换,是经过ioctl
中的KVM_SET_USER_MEMORY_REGION
命令来实现的,以下图:spa
找到了入口,让咱们进一步揭开神秘的面纱。操作系统
关键的数据结构以下:
debug
slot
来组织物理内存,每一个slot
对应一个struct kvm_memory_slot
,一个虚拟机的全部slot
构成了它的物理地址空间;struct kvm_userspace_memory_region
来设置内存slot
,在内核中使用struct kvm_memslots
结构来将kvm_memory_slot
组织起来;struct kvm_userspace_memory_region
结构体中,包含了slot
的ID号用于查找对应的slot
,此外还包含了物理内存起始地址及大小,以及HVA地址,HVA地址是在用户进程地址空间中分配的,也就是Qemu进程地址空间中的一段区域;数据结构部分已经罗列了大致的关系,那么在KVM_SET_USER_MEMORY_REGION
时,围绕的操做就是slots
的建立、删除,更新等操做,话很少说,来图了:3d
__kvm_set_memory_region
函数,在该函数中完成全部的逻辑处理;__kvm_set_memory_region
函数,首先会对传入的struct kvm_userspace_memory_region
的各个字段进行合法性检测判断,主要是包括了地址的对齐,范围的检测等;slot
索引号,去查找虚拟机中对应的slot
,查找的结果只有两种:1)找到一个现有的slot;2)找不到则新建一个slot;memory_size
为0,那么会将对应slot
进行删除操做;slot
的处理方式:KVM_MR_CREATE
,KVM_MR_MOVE
,KVM_MEM_READONLY
;kvm_set_memslot
来设置和更新slot
信息;具体的memslot
的设置在kvm_set_memslot
函数中完成,slot
的操做流程以下:
memslots
,并将原来的memslots
内容复制到新的memslots
中;slot
的操做是删除或者移动,首先根据旧的slot id
号从memslots
中找到原来的slot
,将该slot
设置成不可用状态,再将memslots
安装回去。这个安装的意思,就是RCU的assignment操做,不理解这个的,建议去看看以前的RCU系列文章。因为slot
不可用了,须要解除stage2的映射;kvm_arch_prepare_memory_region
函数,用于处理新的slot
可能跨越多个用户进程VMA区域的问题,若是为设备区域,还须要将该区域映射到Guest IPA
中;update_memslots
用于更新整个memslots
,memslots
基于PFN来进行排序的,添加、删除、移动等操做都是基于这个条件。因为都是有序的,所以能够选择二分法来进行查找操做;slot
后的memslots
安装回KVM中;kvfree
用于将原来的memslots
释放掉;kvm_delete_memslot
函数,实际就是调用的kvm_set_memslot
函数,只是slot
的操做设置成KVM_MR_DELETE
而已,再也不赘述。
光有了GPA->HVA,彷佛仍是跟Hypervisor
没有太大关系,究竟是怎么去访问物理内存的呢?貌似也没有看到去创建页表映射啊?
跟我走吧,带着问题出发!
以前内存管理相关文章中提到过,用户态程序中分配虚拟地址vma后,实际与物理内存的映射是在page fault
时进行的。那么一样的道理,咱们能够顺着这个思路去查找是否HVA->HPA的映射也是在异常处理的过程当中建立的?答案是显然的。
回顾一下前文《Linux虚拟化KVM-Qemu分析(四)之CPU虚拟化(2)》
的一张图片:
kvm_arch_vcpu_ioctl_run
时,会让Guest OS
去跑在Hypervisor
上,当Guest OS
中出现异常退出到Host
时,此时handle_exit
将对退出的缘由进行处理;异常处理函数arm_exit_handlers
以下,具体调用选择哪一个处理函数,是根据ESR_EL2, Exception Syndrome Register(EL2)
中的值来肯定的。
static exit_handle_fn arm_exit_handlers[] = { [0 ... ESR_ELx_EC_MAX] = kvm_handle_unknown_ec, [ESR_ELx_EC_WFx] = kvm_handle_wfx, [ESR_ELx_EC_CP15_32] = kvm_handle_cp15_32, [ESR_ELx_EC_CP15_64] = kvm_handle_cp15_64, [ESR_ELx_EC_CP14_MR] = kvm_handle_cp14_32, [ESR_ELx_EC_CP14_LS] = kvm_handle_cp14_load_store, [ESR_ELx_EC_CP14_64] = kvm_handle_cp14_64, [ESR_ELx_EC_HVC32] = handle_hvc, [ESR_ELx_EC_SMC32] = handle_smc, [ESR_ELx_EC_HVC64] = handle_hvc, [ESR_ELx_EC_SMC64] = handle_smc, [ESR_ELx_EC_SYS64] = kvm_handle_sys_reg, [ESR_ELx_EC_SVE] = handle_sve, [ESR_ELx_EC_IABT_LOW] = kvm_handle_guest_abort, [ESR_ELx_EC_DABT_LOW] = kvm_handle_guest_abort, [ESR_ELx_EC_SOFTSTP_LOW]= kvm_handle_guest_debug, [ESR_ELx_EC_WATCHPT_LOW]= kvm_handle_guest_debug, [ESR_ELx_EC_BREAKPT_LOW]= kvm_handle_guest_debug, [ESR_ELx_EC_BKPT32] = kvm_handle_guest_debug, [ESR_ELx_EC_BRK64] = kvm_handle_guest_debug, [ESR_ELx_EC_FP_ASIMD] = handle_no_fpsimd, [ESR_ELx_EC_PAC] = kvm_handle_ptrauth, };
用你那双水汪汪的大眼睛扫描一下这个函数表,发现ESR_ELx_EC_DABT_LOW
和ESR_ELx_EC_IABT_LOW
两个异常,这不就是指令异常和数据异常吗,咱们大胆的猜想,HVA->HPA
映射的创建就在kvm_handle_guest_abort
函数中。
kvm_handle_guest_abort
先来补充点知识点,能够更方便的理解接下里的内容:
EL2
的el1_sync
(arch/arm64/kvm/hyp/entry-hyp.S
)异常入口;ESR_EL2
寄存器记录了异常产生的缘由;简要看一下ESR_EL2
寄存器:
EC
:Exception class,异常类,用于标识异常的缘由;ISS
:Instruction Specific Syndrome,ISS域定义了更详细的异常细节;kvm_handle_guest_abort
函数中,多处须要对异常进行判断处理;kvm_handle_guest_abort
函数,处理地址访问异常,能够分为两类:
先看一下kvm_handle_guest_abort
函数的注释吧:
/** * kvm_handle_guest_abort - handles all 2nd stage aborts * * Any abort that gets to the host is almost guaranteed to be caused by a * missing second stage translation table entry, which can mean that either the * guest simply needs more memory and we must allocate an appropriate page or it * can mean that the guest tried to access I/O memory, which is emulated by user * space. The distinction is based on the IPA causing the fault and whether this * memory region has been registered as standard RAM by user space. */
调用流程来了:
kvm_vcpu_trap_get_fault_type
用于获取ESR_EL2
的数据异常和指令异常的fault status code
,也就是ESR_EL2
的ISS域;kvm_vcpu_get_fault_ipa
用于获取触发异常的IPA地址;kvm_vcpu_trap_is_iabt
用于获取异常类,也就是ESR_EL2
的EC
,而且判断是否为ESR_ELx_IABT_LOW
,也就是指令异常类型;kvm_vcpu_dabt_isextabt
用于判断是否为同步外部异常,同步外部异常的状况下,若是支持RAS,Host能处理该异常,不须要将异常注入给Guest;FSC_FAULT
,FSC_PERM
,FSC_ACCESS
三种类型的话,直接返回错误;gfn_to_memslot
,gfn_to_hva_memslot_prot
这两个函数,是根据IPA去获取到对应的memslot和HVA地址,这个地方就对应到了上文中第二章节中地址关系的创建了,因为创建了链接关系,即可以经过IPA去找到对应的HVA;KVM_HVA_ERR_BAD
。kvm_is_error_hva
或者(write_fault && !writable)
表明两种错误:1)指令错误,向Guest注入指令异常;2)IO访问错误,IO访问又存在两种状况:2.1)Cache维护指令,则直接跳过该指令;2.2)正常的IO操做指令,调用io_mem_abort
进行IO模拟操做;handle_access_fault
用于处理访问权限问题,若是内存页没法访问,则对其权限进行更新;user_mem_abort
,用于分配更多的内存,实际上就是完成Stage 2页表映射的创建,根据异常的IPA地址,已经对应的HVA,创建映射,细节的地方就不表了。前因后果摸清楚了,那就草草收场吧,下回见了。
《Arm Architecture Registers Armv8, for Armv8-A architecture profile》
欢迎关注我的公众号,不按期分享技术文章。