Skip to content
SonJuHyung edited this page Jan 22, 2018 · 2 revisions

THP 관련

  • THP allocation on page fault 분석(2MB size)
  • khugepaged promotion 분석
  • kcompactd 분석 - ongoing

THP allocation on page fault 분석 (2MB size)

page fault 시 pud, pmd 등을 할당해 주는 __handle_mm_fault 함수에서 pmd entry 가 비어있고, THP 사용 설정되어 있을 경우, create_huge_pmd 호출하여 huge page 할당 수행. pmd entry 가 비어있지 않을 경우 이미 THP 로 사용되고 있던 pmd 에 대한 접근이라면 먼저 NUMA hinting fault 로 진행되어야 하는지 검사한다. automatic NUMA balancing 을 수행해야 하는 경우가 아닐 경우(NUMA hinting page fault 가 아닐 경우), page 에 대한 write 접근 권한 및 현재 fault 가 write 인지 검사하여 read fault 일 경우에는 단순히 accessed bit 설정하고 끝나지만 write fault 일 경우에는 pmd entry 를 통해 THP physical page 에 접근하여 그 내용 다른 2MB 영역에 복사하고 새로운 pmd entry 로 채워 넣는다.

static int __handle_mm_fault(struct vm_area_struct *vma, unsigned long address,
        unsigned int flags)
{
    struct vm_fault vmf = {
        .vma = vma,
        .address = address & PAGE_MASK,
        .flags = flags,
        .pgoff = linear_page_index(vma, address),
        .gfp_mask = __get_fault_gfp_mask(vma),
    };
    struct mm_struct *mm = vma->vm_mm;
    pgd_t *pgd;
    p4d_t *p4d;
    int ret;

    pgd = pgd_offset(mm, address);
    p4d = p4d_alloc(mm, pgd, address);
    if (!p4d)
        return VM_FAULT_OOM;

    vmf.pud = pud_alloc(mm, p4d, address);
    // pud 가 할당되어 있지 않을시 할당
    if (!vmf.pud)
        return VM_FAULT_OOM;
    // address 에 해당하는 pgd, pud 를 얻어옴
    if (pud_none(*vmf.pud) && transparent_hugepage_enabled(vma)) {
        // pud entry 가 비어있고,  현재 THP 설정된 경우 
        ret = create_huge_pud(&vmf);
        // pud 단위 THP인 1G 크기의 THP  할당 시도(1G THP) 
        // - v4.11 에서는 anonymous page 에 대해 pud 단위 THP 아직 지원안함 
        //   => ret 는 VM_FAULT_FALLBACK 
        // - file backed 도 huge_fault 설정 안되있는듯
        if (!(ret & VM_FAULT_FALLBACK))
            return ret;
        // 1G THP 할당이 실패하지 않았다면 여기서 종료 
        // VM_FAULT_FALLBACK 설정시... 1G THP 할당 불가(연속된 영역 없음)
        //  -> 그다음 level 인 2MB 단위 THP 할당 시도
    } else {
        pud_t orig_pud = *vmf.pud;
        // pud entry 가 비어있지 않은 경우
        barrier();
        if (pud_trans_huge(orig_pud) || pud_devmap(orig_pud)) {
            // pud entry 가 THP 이면...
            unsigned int dirty = flags & FAULT_FLAG_WRITE;
            // 현재 page fault 가 write 요청일 경우

            /* NUMA case for anonymous PUDs would go here */

            if (dirty && !pud_write(orig_pud)) {
                // write 접근이지만, pud 에 대해 write 금지가 되어 있다면
                ret = wp_huge_pud(&vmf, orig_pud);
                // 여기도 아직 지원안됨 => ret 는 VM_FAULT_FALLBACK 
                // 원래 목적은 새 page 할당 후, copy 하여 사용
                if (!(ret & VM_FAULT_FALLBACK))
                    return ret;
            } else {
                huge_pud_set_accessed(&vmf, orig_pud);
                // writable 하므로 이거 그냥 사용
                return 0;
            }
        }
    }

    vmf.pmd = pmd_alloc(mm, vmf.pud, address);
    // pmd 가 할당되어 있지 않을 시 할당
    if (!vmf.pmd)
        return VM_FAULT_OOM;
    if (pmd_none(*vmf.pmd) && transparent_hugepage_enabled(vma)) {
        // pmd entry 가 비어 있고, THP 설정이 되어 있을 경우
        ret = create_huge_pmd(&vmf);
        // pmd 단위 THP 인 2MB 크기의 THP 할당 시도(2MB THP)
        if (!(ret & VM_FAULT_FALLBACK))
            return ret;
        // 2MB THP 할당이 실패하지 않았다면 여기서 종료
        // VM_FAULT_FALLBACK 설정시... 2MB THP 할당 불가(연속된 영역 없음)
        //  -> 그 다음 level 인 default page 할당 시도
    } else {
        pmd_t orig_pmd = *vmf.pmd;
        // pmd entry 가 비어있지 않은 경우
        barrier();
        if (pmd_trans_huge(orig_pmd) || pmd_devmap(orig_pmd)) {
            // pmd entry 가 THP 이면...
            if (pmd_protnone(orig_pmd) && vma_is_accessible(vma))
                return do_huge_pmd_numa_page(&vmf, orig_pmd);
            // NUMA hinting page fault 로 수행되어야 한다면 NUMA hint page fault 수행
            //  * NUMA hinting page fault : automatic NUMA balancing 과정
            if ((vmf.flags & FAULT_FLAG_WRITE) &&
                    !pmd_write(orig_pmd)) {
                // write 접근이지만 pmd 가 write 불가능하게 설정된 경우 
                ret = wp_huge_pmd(&vmf, orig_pmd);
                // 새 page 할당 후, copy 하여  사용.
                // 불가능하면 FALLBACK  
                if (!(ret & VM_FAULT_FALLBACK))
                    return ret;
            } else {
                huge_pmd_set_accessed(&vmf, orig_pmd);
                // writeable 하므로 이거 그냥 사용
                return 0;
            }
        }
    }

    return handle_pte_fault(&vmf);
}

pmd entry 가 비어 있는 경우 create_huge_pmd 를 통해 anonymous page 여부 검사를 수행하여 anonymous page 일 경우(filebacked page 의 경우, disk 와 sync 하기 위한 vm_ops 가 설정되어 있음), do_huge_pmd_anonymous_page 를 호출한다.

static int create_huge_pmd(struct vm_fault *vmf)                                                                              
{       
    if (vma_is_anonymous(vmf->vma)) // vm_ops 없으면 즉 anonymous page 라면                                                   
        return do_huge_pmd_anonymous_page(vmf);                                                                               
    if (vmf->vma->vm_ops->huge_fault) // vm_ops 있어서 huge_fault 있으면                                                      
        return vmf->vma->vm_ops->huge_fault(vmf, PE_SIZE_PMD);                                                                
    return VM_FAULT_FALLBACK;
}           

do_huge_pmd_anonymous_page 로 들어가게 되면 먼저 fault 난 address 가 속한 vma 의 범위안에 2MB align address 가 껴있는지 검사하여 아닐 경우, VM_FAULT_FALLBACK 을 통해 default page 로 할당.khugepaged 에 의해 항상 background 로 수행되어야 할 경우, khugeapged 의 scan list 에 현재 fault process 의 mm 을 넣어 주고, khugepaged 가 scan 할 것이 없어 sleep 중이었다면 깨워줌

read fault 라면 pmd entry 에 들어갈 page table을 위한 page 할당 후, huge apge 를 zero filling 해주어야 할 경우, zero huge page 설정되어 있다면(default) 미리 설정된 zero page 읽어 반환. zero huge page 설정되어 있지 않다면 alloc_pages 함수를 통해 512 개의 9 order page 할당 수행.

write fault 라면, sys interface 를 통해 설정된 huge page 설정에 맞게 gfp flag 구성 후, alloc_hugepage_vma 함수를 통해 512 개 hugepage 할당 수행. page 할당이 성공하면, pmd entry 에 들어갈 page table 구성 후, 할당한 512 page 를 zero filling 해주고, ACTIVE_ANON lru 에 추가 등 수행

int do_huge_pmd_anonymous_page(struct vm_fault *vmf)
{
    struct vm_area_struct *vma = vmf->vma;
    gfp_t gfp;
    struct page *page;
    unsigned long haddr = vmf->address & HPAGE_PMD_MASK;
    // fault 난 address 를 2MB 단위 내림 하여 haddr 설정
    
    if (haddr < vma->vm_start || haddr + HPAGE_PMD_SIZE > vma->vm_end)
        return VM_FAULT_FALLBACK;
    // fault 난 vma 영역의 범위(vm_start ~ vm_end) 내에 hddr~hddr+2MB 보다 커야 함. 
    //
    //                        haddr          haddr+HPAGE_PMD_SIZE
    //   1 MB                 2 MB                 3 MB                 4 MB
    //    |                    |    address         |                    |
    //    |                    |      |             |                    |
    // ---#---------------*----#------!-------------#---*----------------#---
    //                    |                             |
    //                  vm_start                      vm_end
    //
    if (unlikely(anon_vma_prepare(vma)))
        return VM_FAULT_OOM;
    if (unlikely(khugepaged_enter(vma, vma->vm_flags)))
        return VM_FAULT_OOM;
    // khugepage 에 의해 background 로 수행되어야 할 경우 khugepaged 의 scan list 에 넣음
    if (!(vmf->flags & FAULT_FLAG_WRITE) &&
            !mm_forbids_zeropage(vma->vm_mm) &&
            transparent_hugepage_use_zero_page()) {
        // read fault 이며, huge page zeroing 해주어야 할 경우
        pgtable_t pgtable;
        struct page *zero_page;
        bool set;
        int ret;
        pgtable = pte_alloc_one(vma->vm_mm, haddr);
        // pmd entry 에 mapping 할page table 을 위한 page 할당
        if (unlikely(!pgtable))
            return VM_FAULT_OOM;
        zero_page = mm_get_huge_zero_page(vma->vm_mm);
        // huge zero page 설정되어 있다면 미리 zero filled  되어 있는 그냥 가져오고 
        // 아니라면 get_huge_zero_page 를 통해 물리 page zero filling 된 것 가져옴 
        if (unlikely(!zero_page)) {
            pte_free(vma->vm_mm, pgtable);
            count_vm_event(THP_FAULT_FALLBACK);
            return VM_FAULT_FALLBACK;
            // fallback 시 base page 할당 
        }   
        vmf->ptl = pmd_lock(vma->vm_mm, vmf->pmd);
        ret = 0;
        set = false;
        if (pmd_none(*vmf->pmd)) {
            if (userfaultfd_missing(vma)) {
                spin_unlock(vmf->ptl);
                ret = handle_userfault(vmf, VM_UFFD_MISSING);
                VM_BUG_ON(ret & VM_FAULT_FALLBACK);
            } else {
                set_huge_zero_page(pgtable, vma->vm_mm, vma,
                           haddr, vmf->pmd, zero_page);
                spin_unlock(vmf->ptl);
                set = true;
            }
        } else
            spin_unlock(vmf->ptl);
        if (!set)
            pte_free(vma->vm_mm, pgtable);
        return ret;
    }
    // write fault 라면
    gfp = alloc_hugepage_direct_gfpmask(vma);
    // sys interface 설정값 통해 gfp flag 설정 
    page = alloc_hugepage_vma(gfp, vma, haddr, HPAGE_PMD_ORDER);
    if (unlikely(!page)) {
        count_vm_event(THP_FAULT_FALLBACK);
        return VM_FAULT_FALLBACK;
    }
    prep_transhuge_page(page);
    return __do_huge_pmd_anonymous_page(vmf, page, gfp);
    // pmd entry 에 들어갈 page table 할당, page clearing, lru 추가 등 수행
}

__handle_mm_fault 함수에서 pmd 가 비어있지 않고 사용중이며 pmd 가 rw bit 가 설정되어 있지 않을 경우에는 ...

Clone this wiki locally