拆分

进入细节前,我们先来看看有哪几种情况会触发拆分。

手动拆分

为了测试split的功能,内核还提供一个调试接口文件/sys/kernel/debug/split_huge_pages。按照格式写入该文件,则会触发对应页面的split。

PS: 也可以参考tools/testing/selftests/mm/split_huge_page_test.c中的使用方法。

使用方法

这个文件在mm/huge_memory.c中创建的,只有写的接口,没有读的。对应的方法是split_huge_pages_write()。

写这个文件分为两种格式: 文件和进程的。这次我们只看进程的。

在split_huge_page_test.c中有定义这个格式:

#define PID_FMT "%d,0x%lx,0x%lx,%d"

也就是 pid, start_vaddr, end_vaddr, new_order

可以看到,因为现在有mTHP了,所以拆分的时候可以指定拆分到的页面大小。当new_order省略时,默认位0。

自动拆分--deferred_split_shrinker

系统运行时一般情况下我们不会去手动拆分大页,而是通过shrinker扫描将浪费的大页拆分 -- deferred_split_shrinker。

shrinker的机制不在本章范围,简单来说函数do_shrink_slab()会在必要的时候调用count_objects/scan_objects。而deferred_split_shrinker的这两个成员是:

  • deferred_split_count

  • deferred_split_scan

前者就是查看对应ds_queue->split_queuue_len,后者则需要执行关键的操作split_folio()。

添加到deferred_list

首先当分配pmd folio的时候,会调用deferred_split_folio()将folio添加到对应的deferred_list中。

这样在deferred_split_scan的时候才会扫描到对应的folio,然后继续做拆分。

判断是否需要拆分

这个任务交给了thp_underused(),就是判断空闲的页面是否达到一定的数量。

拆分

最后重要的步骤就是拆分 -- split_folio()

拆分页表

现在的透明大页还支持拆分,这样可以在必要的时候退回到四级页表。

这个工作由函数__split_huge_pmd_locked完成。

__split_huge_pmd_locked
    pgtable = pgtable_trans_huge_withdraw(mm, pmd);
    pmd_populate(mm, &_pmd, pgtable);
    for (i = 0; i < HPAGE_PMD_NR; i++) {
        set_pte_at(mm, addr, pte, entry);
    }
    pmd_populate(mm, pmd, pgtable);

其实说白了很简单,就是把预留了页表拿出来,每一项都填上大页对应的地址。最后把相应的pmd改好。是不是有点酷。

当然我这个流程中只显示了匿名页的拆分过程,对于文件页还需要继续学习。

拆分大页

拆分页表最终的目的也是为了拆分大页,这样在系统内存不够时可以回收大页中没有使用的部分。

下图是一个非常简化版本的大致流程。

    split_huge_page_to_list
        unmap_page
            try_to_unmap_one
                ...
                __split_huge_pmd_locked
        __split_huge_page
            remap_page

可以看到,拆分大页的过程中,也有可能会拆分页表。此时我们称之为PMD-mapped THP。如果这个大页的页表已经被拆分过了,就不需要也不能再次拆分,我们就叫它PTE-mapped THP。

所以对于匿名大页,整个拆分的过程就分成两种情况

  • PMD-mapped THP

  • PTE-mapped THP

我们分别来看看这两种情况下拆分过程中细微的差别。

对于PMD-mapped THP, __split_huge_pmd_locked函数会执行到。该函数会将PTE entry改写成migration entry。然后在函数remap_page中再将migration entry 恢复到页表中。怎么样,是不是很有意思。

对于PTE-mapped THP, __split_huge_pmd_locked函数不会被执行,因为页表早已拆分。此时try_to_unmap_one函数就担负起将PTE entry设置成migration entry的重任。接下来就和之前一样,由remap_page将migration entry恢复到页表中。

Last updated