-
Notifications
You must be signed in to change notification settings - Fork 2
ETC.
- THP allocation on page fault 분석(2MB size)
- khugepaged promotion 분석
- kcompactd 분석 - ongoing
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 가 설정되어 있지 않을 경우에는 ...