KVM tree commit id:2868b2513aa732a99ea4a0a6bf10dc93c1f3dac2 v4.11.0

kvm_mmu_free_page释放MMU page, kvm_mmu_commit_zap_page负责具体的任务,需要被释放的MMU page都挂在invalid_list链表上,而invalid_list生成方式主要有这几种:kvm_sync_page, kvm_sync_pages,kvm_mmu_prepare_zap_page,prepare_zap_oldest_mmu_page。

kvm_sync_pages只在kvm_mmu_get_page中的影子页表下使用,不管。

但是kvm_mmu_get_page下有__kvm_sync_page,都是调用kvm_mmu_prepare_zap_page来更新invalid_list,先看第一个sp->role.cr4_pae,来自于kvm_mmu_get_page下的

role.direct = direct;

if (role.direct)  role.cr4_pae = 0;

sp->role = role;

而direct很明显为1,如此第一个在64位EPT下不执行。

看第二个,vcpu->arch.mmu.sync_page(vcpu, sp)恒定返回0, 因为在init_kvm_tdp_mmu下,context->sync_page = nonpaging_sync_page;而nonpaging_sync_page肯定返回0,那么这就可以确定在64位下,__kvm_sync_page一定返回false,且执行kvm_mmu_prepare_zap_page(vcpu->kvm, sp, invalid_list)。

kvm_sync_page先是使用kvm_unlink_unsync_page将unsync标志去掉,然后__kvm_sync_page,分析如上。

prepare_zap_oldest_mmu_page则是从kvm->arch.active_mmu_pages挑出最后一个sp,执行kvm_mmu_prepare_zap_page。

到这里看所有的查找过程都走到kvm_mmu_prepare_zap_page。

但是先看kvm_arch下的这两个链表

struct list_head active_mmu_pages;

struct list_head zapped_obsolete_pages;

active_mmu_pages:在kvm_mmu_alloc_page下,所有分配的mmu page都挂到active_mmu_pages上,只在kvm_mmu_prepare_zap_page下有变动

list_move(&sp->link, &kvm->arch.active_mmu_pages);

关于!sp->root_count的判断,root_count只对eptp指向的mmu page有意义,其他mmu page都是0:

root_count: A counter keeping track of how many hardware registers (guest cr3 or pdptrs) are now pointing at the page. While this counter is nonzero, the page cannot be destroyed. See role.invalid.

zapped_obsolete_pages只是一个全局的invalid_list而已,在kvm_zap_obsolete_pages下填充链表了一次,然后kvm_mmu_commit_zap_page,问题在于kvm_mmu_commit_zap_page中invalid_list中的元素并没有摒除,仍然是有效的。

再看kvm_mmu_prepare_zap_page,先是++kvm->stat.mmu_shadow_zapped,然后是mmu_zap_unsync_children,下面有kvm_mmu_page_unlink_children和kvm_mmu_unlink_parents负责清除sp上下级的联系,在mmu_page_zap_pte中主要是rmap和track bit。在mmu_zap_unsync_children下,先使用mmu_unsync_walk,进行遍历收集sp到pages,首先将kvm_mmu_pages下的计数器置0,然后将sp添加到0位置的数组上:

pvec->nr = 0;
mmu_pages_add(pvec, sp, INVALID_INDEX);

__mmu_unsync_walk下则是遍历当前sp->unsync_child_bitmap位图下的所有ent,如果子sp下还有unsync_children或者unsync都要加入pvec下,但是最多加KVM_PAGE_ARRAY_NR个,超过了则返回ENOSPC错误。

返回到mmu_zap_unsync_children

//mmu_unsync_walk负责收集所有的unsync sp,全收集完成后才会返回0,否则会一直进行收集

//每次最多收集16个,递交给kvm_mmu_prepare_zap_page进行处理

while (mmu_unsync_walk(parent, &pages)) {

struct kvm_mmu_page *sp;

for_each_sp(pages, sp, parents, i) {

    kvm_mmu_prepare_zap_page(kvm, sp, invalid_list);

    mmu_pages_clear_parents(&parents);

    zapped++;

}

这样来看invalid_list在mmu_zap_unsync_children下是没有任何改动的,真正的改动在于

if (!sp->root_count) {

list_move(&sp->link, invalid_list);

kvm_mod_used_mmu_pages(kvm, -1);

}

如果sp是非root sp,那么就会被添加到invalid_list上。

那么对于mmu_free_roots而言,mmu page确信没有了作用,那么mmu page就要全部释放,首先拿到root sp,直接执行kvm_mmu_prepare_zap_page和kvm_mmu_commit_zap_page,mmu_zap_unsync_children瞄准的都是unsync的sp,这个比较奇怪,理论上应该干掉所有的sp,

unsync: If true, then the translations in this page may not match the guest's translation. This is equivalent to the state of the tlb when a pte is changed but before the tlb entry is flushed. Accordingly, unsync ptes are synchronized when the guest executes invlpg or flushes its tlb by other means. Valid for leaf pages.

unsync_children: How many sptes in the page point at pages that are unsync (or have unsynchronized children).

unsync_child_bitmap: A bitmap indicating which sptes in spt point (directly or indirectly) at pages that may be unsynchronized. Used to quickly locate all unsychronized pages reachable from a given page.

mark_unsync负责配置unsync_child_bitmap和unsync_children,只有当unsync_children为0时才会执行kvm_mmu_mark_parents_unsync。在link_shadow_page下只有

if (sp->unsync_children || sp->unsync)

mark_unsync(sptep);

clear_unsync_child_bit则负责减少unsync_children和unsync_child_bitmap,没有其他地方更新了,而调用clear_unsync_child_bit只有__mmu_unsync_walk和mmu_pages_clear_parents,也即是只有sync walk之后,unsync才会被清理掉。

kvm_unsync_page则是设置sp->unsync,只有mmu_need_write_protect调度它,且can_unsync从mmu_set_spte传递给set_spte皆为真,在set_spte中,只有pte access有写请求,在check mmu_need_write_protect过程中,执行kvm_unsync_page(vcpu, sp)。

kvm_unlink_unsync_page清理sp->unsync,被kvm_sync_page和kvm_mmu_prepare_zap_page,sync的本质不是让tlb和pte一致,而是直接抛弃unsync的sp,比如kvm_mmu_get_page下的:

if (sp->unsync) {

    /* The page is good, but __kvm_sync_page might still end

     * up zapping it.  If so, break in order to rebuild it.

     */
    if (!__kvm_sync_page(vcpu, sp, &invalid_list))

break;

}

另外考虑到kvm_mmu_page下的unsync,unsync_children和unsync_child_bitmap都只在shadow page table下有效,那么kvm_mmu_prepare_zap_page在TDP模式下就不会捕获子SP下的SP,只会将当前的SP加入到invalid_list。

这么看,所谓的kvm_sync_page也只是回收当前的sp,prepare_zap_oldest_mmu_page回收最早申请的kvm_mmu_page,引发也是因为要控制vm的mmu page个数,mmu_free_roots也只是释放root mmu page,kvm_zap_obsolete_pages则是回收MMU版本号更新的...


KVM MMU page释放机制来自于OenHan

链接为:http://oenhan.com/kvm-free-mmu-page

4 thoughts on “KVM MMU page释放机制”

  1. 1. 你好,mmu_free_roots释放的不只是root mmu page,还有unsync的影子页表,即一级页表。
    mmu_free_roots -> kvm_mmu_prepare_zap_page -> mmu_zap_unsync_children

    while (mmu_unsync_walk(parent, &pages)) {

    struct kvm_mmu_page *sp;

    for_each_sp(pages, sp, parents, i) {

    kvm_mmu_prepare_zap_page(kvm, sp, invalid_list);

    mmu_pages_clear_parents(&parents);

    zapped++;

    }

    mmu_zap_unsync_children按深度优先的方式搜索unsync的page。
    for_each_sp(pages, sp, parents, i){}循环体里面应该zap unsync的影子页表 及
    减少影子页表的unsync_children和unsync_child_bitmap。

    请问理解的对不对?

    2. 当调用kvm_mmu_prepare_zap_page准备zap影子页表时会与parent pages及child page解除关联关系。
    如果child page的parent_ptes链表是空的,按理说child page已经不在影子页表中了,可以回收了,
    但是在kvm代码中没有找到相关的处理逻辑,感觉挺奇怪。
    不清楚作者是出于什么目的,是暂存到active_mmu_pages链表中,减少内存分配的开销吗?

    1. @XYZ unsync只在影子页表中是有效的,在ept页表中是无用的,文中涉及到的都是ept页表,你提到的影子页表是指有别于ept页表的影子页表么?

  2. kvm_mmu_page下的unsync,unsync_children和unsync_child_bitmap都只在shadow page table下有效。这个是如何确定的呢,使用EPT也可以从direct_page_fault -> __direct_map -> link_shadow_page这里调用到mark_unsync函数来设置unsync_child_bitmap和unsync_children吧?

发表回复