影子页表虚拟化-----反向映射

580阅读 0评论2014-01-10 embeddedlwp
分类:LINUX

反向映射
   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;
        }
}
上一篇:客户机物理页框到宿主机虚拟地址转换
下一篇:虚拟MMU---客户机页表遍历