qemu-kvm 内存虚拟化---影子页表
影子页表被载入到真正硬件MMU中,通过影子页表进行客户机虚拟地址到宿主机物理地址(gva--->hpa)的转换。
由于影子页表项不存在或者访问权限不够,可能导致页故障,这页故障由物理硬件MMU产生的,这些故障被截获,并且进行处理和模拟。
影子页表页故障处理流程:
1.首先遍历客户机页表,获取页表的结构和内容,判断客户机页表的权限是否足够,如果不足够,注入客户机页故障中断,让返回客户机模式,客户机去执行。否则进入2.
2.获取客户机物理页框在宿主机的物理地址,如果宿主机的物理地址不存在,可以断定是mmio,返回1,调用相应模拟函数进行处理,进入4,如果不是mmio进入5.(mmio通过不映射,而进行截获和模拟)
3.遍历影子页表,完成创建影子页表(填充影子页表),再填充过程中,如果客户机页目录结构页对应影子页表页表项标记为写保护,目的截获对于页目录的修改。否则进入4
4.如果客户机页表项页对应影子页表页表项数据结构标记为不同步,即取消同步,让返回客户机模式,客户机去执行.
5.模拟指令执行,如果失败,让返回客户机模式,客户机重新执行.否则进入6
6.模拟指令执行,完成修改客户机页表,修改影子页表
上述6步影子页表页故障处理流程
int kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gva_t cr2, u32 error_code)
{
int r;
enum emulation_result er;
r = vcpu->arch.mmu.page_fault(vcpu, cr2, error_code);//如果r==0(不是写故障),故障处理结束。
if (r < 0)
goto out;
if (!r) {
r = 1;
goto out;
}
r = mmu_topup_memory_caches(vcpu);
if (r)
goto out;
er = emulate_instruction(vcpu, cr2, error_code, 0); 如果r==1(写故障),模拟客户机出现故障指令
switch (er) {
case EMULATE_DONE:
return 1;
case EMULATE_DO_MMIO:
++vcpu->stat.mmio_exits;
return 0;
case EMULATE_FAIL:
vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR;
vcpu->run->internal.suberror = KVM_INTERNAL_ERROR_EMULATION;
vcpu->run->internal.ndata = 0;
return 0;
default:
BUG();
}
out:
return r;
}
static int FNAME(page_fault)(struct kvm_vcpu *vcpu, gva_t addr,
u32 error_code)
{
//首先遍历客户机页表,获取页表的结构和内容
r = FNAME(walk_addr)(&walker, vcpu, addr, write_fault, user_fault,
fetch_fault);
//注入客户机页故障中断,让返回客户机模式,客户机去执行
if (!r) {
pgprintk("%s: guest page fault\n", __func__);
inject_page_fault(vcpu, addr, walker.error_code);
vcpu->arch.last_pt_write_count = 0; /* reset fork detector */
return 0;
}
if (walker.level >= PT_DIRECTORY_LEVEL) {
level = min(walker.level, mapping_level(vcpu, walker.gfn));
walker.gfn = walker.gfn & ~(KVM_PAGES_PER_HPAGE(level) - 1);
}
pfn = gfn_to_pfn(vcpu->kvm, walker.gfn);
//如果宿主机的物理地址不存在,可以断定是mmio,返回1,进入emulate_instruction()函数
if (is_error_pfn(pfn)) {
pgprintk("gfn %lx is mmio\n", walker.gfn);
kvm_release_pfn_clean(pfn);
return is_fault_pfn(pfn) ? -EFAULT : 1;
}
//遍历影子页表,完成创建影子页表(填充影子页表)
sptep = FNAME(fetch)(vcpu, addr, &walker, user_fault, write_fault,
level, &write_pt, pfn);
if (!write_pt)
vcpu->arch.last_pt_write_count = 0; /* reset fork detector */
return write_pt;
}
//遍历影子页表,完成创建影子页表(填充影子页表),再填充过程中,如果客户机页目录结构页对应影子页表页表项标记为写保护,目的截获对于页目录的修改。如果客户机页表项页对应影子页表页表项数据结构标记为不同步,即取消同步,让返回客户机模式,客户机去执行.
static u64 *FNAME(fetch)(struct kvm_vcpu *vcpu, gva_t addr,
struct guest_walker *gw,
int user_fault, int write_fault, int hlevel,
int *ptwrite, pfn_t pfn)
{
for_each_shadow_entry(vcpu, addr, iterator) {
level = iterator.level;
sptep = iterator.sptep;
if (iterator.level == hlevel) {
printk("%s,hlevel=%d,iterator.level=%d\n",__func__,hlevel,iterator.level);
mmu_set_spte(vcpu, sptep, access,
gw->pte_access & access,
user_fault, write_fault,
gw->ptes[gw->level-1] & PT_DIRTY_MASK,
ptwrite, level,
gw->gfn, pfn, false, true);
break;
}
direct = 0;
table_gfn = gw->table_gfn[level - 2];
printk("%s,table_gfn:%lu,level-1:%d\n",__func__,table_gfn,level-1);
shadow_page = kvm_mmu_get_page(vcpu, table_gfn, addr, level-1,
direct, access, sptep);
if (!direct) {
r = kvm_read_guest_atomic(vcpu->kvm,
gw->pte_gpa[level - 2],
&curr_pte, sizeof(curr_pte));
spte = __pa(shadow_page->spt)
| PT_PRESENT_MASK | PT_ACCESSED_MASK
| PT_WRITABLE_MASK | PT_USER_MASK;
*sptep = spte;
}
return sptep;
}
//如果客户机页目录结构页对应影子页表页表项标记为写保护,目的截获对于页目录的修改。返回1,进入emulate_instruction()函数如果客户机页表项页对应影子页表页表项数据结构标记为不同步,即取消同步,kvm_unsync_page返回0,让返回客户机模式,客户机去执行。
static int mmu_need_write_protect(struct kvm_vcpu *vcpu, gfn_t gfn,
bool can_unsync)
{
struct kvm_mmu_page *shadow;
shadow = kvm_mmu_lookup_page(vcpu->kvm, gfn);
if (shadow) {
printk("shadow->role.level:%d,shadow->unsync:%d,can_unsync:%d\n",shadow->role.level,shadow->unsync,can_unsync);
if (shadow->role.level != PT_PAGE_TABLE_LEVEL) //客户机页目录结构页,返回1,对应影子页表页表项标记为写保护
return 1;
if (shadow->unsync)
return 0;
if (can_unsync && oos_shadow) //客户机页表项页对应影子页表页表项数据结构标记为不同步
return kvm_unsync_page(vcpu, shadow);
return 1;
}
return 0;
}
int emulate_instruction(struct kvm_vcpu *vcpu,
unsigned long cr2,
u16 error_code,
int emulation_type)
{
//解码客户机执行的指令,解码失败话,让返回客户机模式,客户机去执行
r = x86_decode_insn(&vcpu->arch.emulate_ctxt, &emulate_ops);
if (r) {
++vcpu->stat.insn_emulation_fail;
if (reexecute_instruction(vcpu, cr2))
return EMULATE_DONE;
return EMULATE_FAIL;
}
//模拟客户机执行的指令,模拟失败话,让返回客户机模式,客户机去执行
r = x86_emulate_insn(&vcpu->arch.emulate_ctxt, &emulate_ops);
if (r) {
if (reexecute_instruction(vcpu, cr2))
return EMULATE_DONE;
if (!vcpu->mmio_needed) {
kvm_report_emulation_failure(vcpu, "mmio");
return EMULATE_FAIL;
}
return EMULATE_DO_MMIO;
}
}