反向映射
mmu维护一个反向映射, 因此映射这个页的所有页表项,由gfn(客户机页框号)找到映射此页的影子页表项spte。这些情况下:当客户机的宿主机物理页面被交换到硬盘上或者被回收,这是有用的。
为什么有用呢?
如果客户机的宿主机物理页面被交换到硬盘,影子页表肯定要知道,并且要更新影子页表,设置不存在。下一次客户机访问被交换出去页面,查询影子页表被截获,进行处理。如何更新影子页表呢?其实也很简单,就是宿主机进行相应页表回收时,调用注册更新影子页表的函数。更新影子页表页肯定要知道地址?
如何计算影子页表地址呢?
1.页面回收或者交换时,已知宿主机线性地址(虚拟地址)---hva,不然如何回收呢?
2.通过hva可以计算出gfn,如何计算呢?查看上面一篇《客户机物理页框到宿主机虚拟地址转换gfn--->hva》,你可能说不是反了吗?应该是hva---->gfn,且慢公式为:hva=base_hva+(gfn-base_gfn)*PAGE_SIZE,那么gfn=(hva-base_hva)>>PAGE_SIZE+base_gfn 当然base_gfn,base_hva已知
3.通过步骤2获取gfn(客户机物理页框),那么可以通过反向映射定位到影子页表项spte
4.然后设置页表项为空,就可以。
下面代码反向映射的相关操作:反向映射转换,添加,删除。
为了代码分析简单假设反向映射是1:1即gfn:rmap
/*
* Take gfn and return the reverse mapping to it.
* Note: gfn must be unaliased before this function get called
*/
以gfn为参数,返回反向映射的指针。由代码可见likely语句可知,正常情况下leve= PT_PAGE_TABLE_LEVEL=1, 下面代码不分析了。
static unsigned long *gfn_to_rmap(struct kvm *kvm, gfn_t gfn, int level)
{
struct kvm_memory_slot *slot;
unsigned long idx;
slot = gfn_to_memslot(kvm, gfn);
if (likely(level == PT_PAGE_TABLE_LEVEL))
return &slot->rmap[gfn - slot->base_gfn];
..................
}
添加gfn的反向映射值spte
static int rmap_add(struct kvm_vcpu *vcpu, u64 *spte, gfn_t gfn)
{
struct kvm_mmu_page *sp;
struct kvm_rmap_desc *desc;
unsigned long *rmapp;
int i, count = 0;
if (!is_rmap_spte(*spte)) //影子页表项没有映射返回
return count;
gfn = unalias_gfn(vcpu->kvm, gfn);//判断gfn是否由别名,存在话采用别名的gfn(客户机物理页框),实际重新映射。
sp = page_header(__pa(spte));//通过影子页表项找到该页的数据结构
sp->gfns[spte - sp->spt] = gfn;//记录gfn(客户机物理页框)到该页的缓存中
rmapp = gfn_to_rmap(vcpu->kvm, gfn, sp->role.level);//获取gfn(客户机物理页框)反向映射的指针
if (!*rmapp) {
rmap_printk("rmap_add: %p %llx 0->1\n", spte, *spte);
*rmapp = (unsigned long)spte;//填写影子页表项到反向映射的指针
}
return count;
}
static void rmap_remove(struct kvm *kvm, u64 *spte)
{
struct kvm_rmap_desc *desc;
struct kvm_rmap_desc *prev_desc;
struct kvm_mmu_page *sp;
pfn_t pfn;
unsigned long *rmapp;
int i;
if (!is_rmap_spte(*spte))//影子页表项没有映射返回
return;
sp = page_header(__pa(spte));//通过影子页表项找到该页的数据结构
pfn = spte_to_pfn(*spte);//获取影子页表项的内容宿主机物理页页框pfn
if (*spte & shadow_accessed_mask)//如果宿主机物理页被访问过,设置该页page数据结构标志为访问
kvm_set_pfn_accessed(pfn);
if (is_writeble_pte(*spte))//如果宿主机物理页可写,设置该页page数据结构标志为脏
kvm_set_pfn_dirty(pfn);
rmapp = gfn_to_rmap(kvm, sp->gfns[spte - sp->spt], sp->role.level);
if (!*rmapp) {
printk(KERN_ERR "rmap_remove: %p %llx 0->BUG\n", spte, *spte);
BUG();
} else if (!(*rmapp & 1)) { //反向映射存在,设置反向映射的数据项为0.
rmap_printk("rmap_remove: %p %llx 1->0\n", spte, *spte);
if ((u64 *)*rmapp != spte) {
printk(KERN_ERR "rmap_remove: %p %llx 1->BUG\n",
spte, *spte);
BUG();
}
*rmapp = 0;
}
}
对于反向映射是1:1,如果spte为NULL,返回自身。不为空返回,返回反向映射中spte。
static u64 *rmap_next(struct kvm *kvm, unsigned long *rmapp, u64 *spte)
{
struct kvm_rmap_desc *desc;
struct kvm_rmap_desc *prev_desc;
u64 *prev_spte;
int i;
if (!*rmapp)
return NULL;
else if (!(*rmapp & 1)) {
if (!spte)
return (u64 *)*rmapp;
return NULL;
}
}