请先阅读博文 《 __direct_map 函数解析之影子页表的构建》
感谢Intel OTC的 wufeng、OenHan、chenhe、ruanshua、社区的@heart2011给予的帮助和支持
一、kvm_mmu_page 结构
在《KVM的vMMU相关数据结构及其影子页表关系分析》中,我已经讲解了kvm_mmu_page结构的含义和租用,以及同影子页表的关系,我们这里就不再赘述了,这里讲一讲上次没有提到的几个成员
spt 成员指向影子页表页,页中被分为多个spte。影子页表用的页面成为shadow page,一个页面中分为多个表项,每个表项成为spte,注意不论哪个级别,表项都成为spte。这个影子页表页的page描述符中的private指向了管理该页表页的kvm_mmu_page成员。
一个页表页,会被会被多个上级页表项引用到,那么所有引用该页表页的上级页表项称为parent_spte,存放在kvm_mmu_page的parent_ptes链表中,KVM给出了相关的辅助函数,以便方向查找的时候使用
static void mmu_page_add_parent_pte(struct kvm_vcpu *vcpu,
struct kvm_mmu_page *sp, u64 *parent_pte)
{
if (!parent_pte)
return;
pte_list_add(vcpu, parent_pte, &sp->parent_ptes);
}
mmu_valid_gen:为有效版本号,开始时候,同vcpu->kvm->arch.mmu_valid_gen的版本号相同。当发现这两个版本号不相同的时候,说明页表页时无效的,可以快速碾压失效该页表页所管理的GUEST物理地址空间
hlist_add_head(&sp->hash_link,
&vcpu->kvm->arch.mmu_page_hash[kvm_page_table_hashfn(gfn)]);
同时只要是激活的页表页,也会加入arch.active_mmu_page链表中,以便在后面快速的释放内存
二、gfn参数的意义和其辅助函数
gfn在《 __direct_map 函数解析之影子页表的构建》一文中已经提到讲过了,它是该页表页所管理GUEST物理地址空间的起始gfn,而不是发生缺页地址所在页面的gfn。缺页gfn是被管理的GEUST物理地址空间中的一个值
KVM中利用起始gfn在哈希表arch.mmu_page_hash[]上,快速的查找管理该物理地址空间的页表页是否已经被分配了,代码如下
#define for_each_gfn_sp(_kvm, _sp, _gfn) \
hlist_for_each_entry(_sp, \
&(_kvm)->arch.mmu_page_hash[kvm_page_table_hashfn(_gfn)], hash_link) \
if ((_sp)->gfn != (_gfn)) {} else
for_each_gfn_sp(vcpu->kvm, sp, gfn) {
......
mmu_page_add_parent_pte(vcpu, sp, parent_pte);
......
return sp;
}
.细心的同学可能会问到,一个缺页gfn,既在level3页表页的管理空间内,也在level2页表页的管理空间内,那么gfn应该是哪个页表页的起始gfn呢?答案很简单,是level2的起始gfn。因为在__direct_map函数中,是从level4逐级往下处理的,当处理level3页表页的spte的时候,gfn为level3页表页管理GUEST物理地址空间的起始gfn,单发现已经有了指向level2页表页的基地址了,就不会调用kvm_mmu_get_page了;下一次循环的时候,gfn就被变更为level2页表页管理GUEST物理地址空间的起始gfn了,而spte中没有指向level1页表页基地址的指针,才会调用kvm_mmu_get_page,这时候传入的gfn是level2页表页管理GUEST物理地址空间的起始gfn。
三、 kvm_mmu_get_page 函数处理逻辑
static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu,
gfn_t gfn,
gva_t gaddr,
unsigned level,
int direct,
unsigned access,
u64 *parent_pte)
{
....
/*设置角色,重要的是level,代表本页表页在影子页表中的级别*/
role = vcpu->arch.mmu.base_role;
role.level = level;
role.direct = direct;
if (role.direct)
role.cr4_pae = 0;
role.access = access;
......
/*
* 超找页表页是否已经被其他上级的SPTE分配过了,如果是的话,就找到了,返回
* 注意需要将本次的parent_spte加入到反向链表中
*/
for_each_gfn_sp(vcpu->kvm, sp, gfn) {
......
mmu_page_add_parent_pte(vcpu, sp, parent_pte);
......
return sp;
}
......
/*没有分配页表页,则分配出来页表页及其管理结构*/
sp = kvm_mmu_alloc_page(vcpu, parent_pte, direct);
......
/*设置所管理GUEST物理地址空间的起始gfn和level角色*/
sp->gfn = gfn;
sp->role = role;
/*以所管理GUEST物理地址空间的起始gfn为key,加入到hash表arch.mmu_page_hash中*/
hlist_add_head(&sp->hash_link,
&vcpu->kvm->arch.mmu_page_hash[kvm_page_table_hashfn(gfn)]);
/*初始化版本号*/
sp->mmu_valid_gen = vcpu->kvm->arch.mmu_valid_gen;
/*初始化清空页表页中所有的SPTE为0*/
init_shadow_page_table(sp);
......
return sp;
}
有了前两节的背景知识,是不是感觉这段代码弱到爆?不解释了,自己看吧