Rmap -3- (PVMW)

<kernel v5.0>

Rmap -3- (PVMW)

 

PVMW(Page Vma Mapped Walk)

페이지가 VMA에 매핑되었는지 여부를 체크하는 인터페이스이다.

 

다음 그림은 가상 주소에 해당하는 물리 페이지 매핑 또는 swap 엔트리로의 매핑이 되었는지 여부를 보여준다.

 

요청 플래그

PVMW에 사용하는 플래그들과 사용하는 함수들은 다음과 같다.

  • PVMW_SYNC
    • map_pte() 함수에서 사용되며 엄격한 체크 루틴을 무시하게 한다.
    • page_mkclean_one()
    • page_mapped_in_vma()
    • remove_migration_pte()
  • PVMW_MIGRATION
    • migration 엔트리에 매핑되었는지 체크하게 한다.
    • remove_migration_pte()
  • no 플래그
    • try_to_unmap_one()
    • page_referenced_one()
    • __replace_page()
    • page_idle_clear_pte_refs_one()
    • write_protect_page()

 

페이지 vma 매핑 여부 확인

page_vma_mapped_walk()

mm/page_vma_mapped.c -1/2-

/**
 * page_vma_mapped_walk - check if @pvmw->page is mapped in @pvmw->vma at
 * @pvmw->address
 * @pvmw: pointer to struct page_vma_mapped_walk. page, vma, address and flags
 * must be set. pmd, pte and ptl must be NULL.
 *
 * Returns true if the page is mapped in the vma. @pvmw->pmd and @pvmw->pte point
 * to relevant page table entries. @pvmw->ptl is locked. @pvmw->address is
 * adjusted if needed (for PTE-mapped THPs).
 *
 * If @pvmw->pmd is set but @pvmw->pte is not, you have found PMD-mapped page
 * (usually THP). For PTE-mapped THP, you should run page_vma_mapped_walk() in
 * a loop to find all PTEs that map the THP.
 *
 * For HugeTLB pages, @pvmw->pte is set to the relevant page table entry
 * regardless of which page table level the page is mapped at. @pvmw->pmd is
 * NULL.
 *
 * Retruns false if there are no more page table entries for the page in
 * the vma. @pvmw->ptl is unlocked and @pvmw->pte is unmapped.
 *
 * If you need to stop the walk before page_vma_mapped_walk() returned false,
 * use page_vma_mapped_walk_done(). It will do the housekeeping.
 */
bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw)
{
        struct mm_struct *mm = pvmw->vma->vm_mm;
        struct page *page = pvmw->page;
        pgd_t *pgd;
        p4d_t *p4d;
        pud_t *pud;
        pmd_t pmde;

        /* The only possible pmd mapping has been handled on last iteration */
        if (pvmw->pmd && !pvmw->pte)
                return not_found(pvmw);

        if (pvmw->pte)
                goto next_pte;

        if (unlikely(PageHuge(pvmw->page))) {
                /* when pud is not present, pte will be NULL */
                pvmw->pte = huge_pte_offset(mm, pvmw->address,
                                            PAGE_SIZE << compound_order(page));
                if (!pvmw->pte)
                        return false;

                pvmw->ptl = huge_pte_lockptr(page_hstate(page), mm, pvmw->pte);
                spin_lock(pvmw->ptl);
                if (!check_pte(pvmw))
                        return not_found(pvmw);
                return true;
        }
restart:
        pgd = pgd_offset(mm, pvmw->address);
        if (!pgd_present(*pgd))
                return false;
        p4d = p4d_offset(pgd, pvmw->address);
        if (!p4d_present(*p4d))
                return false;
        pud = pud_offset(p4d, pvmw->address);
        if (!pud_present(*pud))
                return false;
        pvmw->pmd = pmd_offset(pud, pvmw->address);
        /*
         * Make sure the pmd value isn't cached in a register by the
         * compiler and used as a stale value after we've observed a
         * subsequent update.
         */
        pmde = READ_ONCE(*pvmw->pmd);
        if (pmd_trans_huge(pmde) || is_pmd_migration_entry(pmde)) {
                pvmw->ptl = pmd_lock(mm, pvmw->pmd);
                if (likely(pmd_trans_huge(*pvmw->pmd))) {
                        if (pvmw->flags & PVMW_MIGRATION)
                                return not_found(pvmw);
                        if (pmd_page(*pvmw->pmd) != page)
                                return not_found(pvmw);
                        return true;
                } else if (!pmd_present(*pvmw->pmd)) {
                        if (thp_migration_supported()) {
                                if (!(pvmw->flags & PVMW_MIGRATION))
                                        return not_found(pvmw);
                                if (is_migration_entry(pmd_to_swp_entry(*pvmw->pmd))) {
                                        swp_entry_t entry = pmd_to_swp_entry(*pvmw->pmd);

                                        if (migration_entry_to_page(entry) != page)
                                                return not_found(pvmw);
                                        return true;
                                }
                        }
                        return not_found(pvmw);
                } else {
                        /* THP pmd was split under us: handle on pte level */
                        spin_unlock(pvmw->ptl);
                        pvmw->ptl = NULL;
                }
        } else if (!pmd_present(pmde)) {
                return false;
        }
        if (!map_pte(pvmw))
                goto next_pte;

@pvmw->page가 @pvmw->vma에 이미 매핑되어 있는지 여부를 반환한다.

  • 코드 라인 11~12에서 pmd만 매핑이 있고, pte는 없는 경우 page_vma_mappled_walk를 완료하고, false를 반환한다.
  • 코드 라인 14~15에서 pte 매핑이 있는 경우 곧바로 next_pte 레이블로 이동한다.
  • 코드 라인 17~29에서 낮은 확률로 huge 페이지인 경우 pte를 지정한 후 함수를 빠져나간다.
  • 코드 라인 30~40에서 restart: 레이블이다. pgd -> p4d -> pud -> pmd 엔트리까지 구한다. 단 엔트리가 없으면 false를 반환한다.
  • 코드 라인 46~72 pmd 엔트리가 trans huge이거나 migration 엔트리인 경우에 대한 처리이다. pmd 엔트리까지의 단계로 완료되는 경우 true를 반환하고, 그렇지 않거나 적절하지 않은 요청인 경우 fasle를  반환한다. 단 split 되는 중이면 ptl 락을 풀고 계속 진행한다.
  • 코드 라인 73~75에서 pmd 엔트리가 존재하지 않는 경우 false를 반환한다.
  • 코드 라인 76~77에서 pte 엔트리가 매핑되지 않은 경우 next_pte 레이블로 이동한다.

 

mm/page_vma_mapped.c -2/2-

        while (1) {
                if (check_pte(pvmw))
                        return true;
next_pte:
                /* Seek to next pte only makes sense for THP */
                if (!PageTransHuge(pvmw->page) || PageHuge(pvmw->page))
                        return not_found(pvmw);
                do {
                        pvmw->address += PAGE_SIZE;
                        if (pvmw->address >= pvmw->vma->vm_end ||
                            pvmw->address >=
                                        __vma_address(pvmw->page, pvmw->vma) +
                                        hpage_nr_pages(pvmw->page) * PAGE_SIZE)
                                return not_found(pvmw);
                        /* Did we cross page table boundary? */
                        if (pvmw->address % PMD_SIZE == 0) {
                                pte_unmap(pvmw->pte);
                                if (pvmw->ptl) {
                                        spin_unlock(pvmw->ptl);
                                        pvmw->ptl = NULL;
                                }
                                goto restart;
                        } else {
                                pvmw->pte++;
                        }
                } while (pte_none(*pvmw->pte));

                if (!pvmw->ptl) {
                        pvmw->ptl = pte_lockptr(mm, pvmw->pmd);
                        spin_lock(pvmw->ptl);
                }
        }
}
  • 코드 라인 1~3에서 pte 엔트리들을 순회하며 매 페이지가 매핑되어 있는지 엄격히 체크한 후 확인이 된 경우 true를 반환한다.
  • 코드 라인 4~7에서 next_pte: 레이블이다. thp가 아니거나 huge 페이지인 경우 page_vma_mappled_walk를 완료하고, false를 반환한다.
  • 코드 라인 8~26에서 매핑된 pte 엔트리가 없는 경우 반복하며 주소가 vma 영역 범위를 벗어난 경우 page_vma_mappled_walk를 완료하고, false를 반환한다.
  • 코드 라인 28~31에서 ptl 락이 해제된 경우 다시 lock을 획득하고 반복한다.

 

다음 그림은 page_vma_mapped_walk() 함수를 통해 해당 주소가 매핑이 잘되었는지 여부를 알아오는 모습을 보여준다.

 

check_pte()

mm/page_vma_mapped.c

/**
 * check_pte - check if @pvmw->page is mapped at the @pvmw->pte
 *
 * page_vma_mapped_walk() found a place where @pvmw->page is *potentially*
 * mapped. check_pte() has to validate this.
 *
 * @pvmw->pte may point to empty PTE, swap PTE or PTE pointing to arbitrary
 * page.
 *
 * If PVMW_MIGRATION flag is set, returns true if @pvmw->pte contains migration
 * entry that points to @pvmw->page or any subpage in case of THP.
 *
 * If PVMW_MIGRATION flag is not set, returns true if @pvmw->pte points to
 * @pvmw->page or any subpage in case of THP.
 *
 * Otherwise, return false.
 *
 */
tatic bool check_pte(struct page_vma_mapped_walk *pvmw)
{
        unsigned long pfn;

        if (pvmw->flags & PVMW_MIGRATION) {
                swp_entry_t entry;
                if (!is_swap_pte(*pvmw->pte))
                        return false;
                entry = pte_to_swp_entry(*pvmw->pte);

                if (!is_migration_entry(entry))
                        return false;

                pfn = migration_entry_to_pfn(entry);
        } else if (is_swap_pte(*pvmw->pte)) {
                swp_entry_t entry;

                /* Handle un-addressable ZONE_DEVICE memory */
                entry = pte_to_swp_entry(*pvmw->pte);
                if (!is_device_private_entry(entry))
                        return false;

                pfn = device_private_entry_to_pfn(entry);
        } else {
                if (!pte_present(*pvmw->pte))
                        return false;

                pfn = pte_pfn(*pvmw->pte);
        }

        return pfn_in_hpage(pvmw->page, pfn);
}

페이지가 매핑되어 있는지 여부를 반환한다. (pvmw->pte 엔트리  —> pvmw->page 매핑, 단 migration 시 swap & migration 엔트리 매핑 여부)

  • 코드 라인 5~14에서 PVMW_MIGRATOIN 플래그 요청이 있는 경우 swap pte 엔트리에 대한 pfn을 알아온다. 만일 swap pte가 아니거나 migration 엔트리가 아닌 경우 false를 반환한다.
  • 코드 라인 15~23에서 swap pte 엔트리에 매핑된 경우 false를 반환하는데 만일 swap pte 엔트리가 디바이스 private 엔트리인 경우 해당 pfn를 알아온다.
    • swap 장치가 버디에서 관리하지 않는 HMM 메모리에 swap된 경우이다.
  • 코드 라인 24~29에서 pte 엔트리가 정상 매핑된 경우 해당 페이지의 pfn을 알아온다.
  • 코드 라인 31에서 산출된 pfn이 일반 페이지 또는 huge 페이지인 pvmw->page의 pfn 범위이내에 존재하는지 여부를 반환한다.

 

다음 그림은 pwmc->address에 해당하는 pwmc->pte 엔트리가 pwmc->page에 매핑이 되었는지 체크한다.

 

map_pte()

mm/page_vma_mapped.c

static bool map_pte(struct page_vma_mapped_walk *pvmw)
{
        pvmw->pte = pte_offset_map(pvmw->pmd, pvmw->address);
        if (!(pvmw->flags & PVMW_SYNC)) {
                if (pvmw->flags & PVMW_MIGRATION) {
                        if (!is_swap_pte(*pvmw->pte))
                                return false;
                } else {
                        /*
                         * We get here when we are trying to unmap a private
                         * device page from the process address space. Such
                         * page is not CPU accessible and thus is mapped as
                         * a special swap entry, nonetheless it still does
                         * count as a valid regular mapping for the page (and
                         * is accounted as such in page maps count).
                         *
                         * So handle this special case as if it was a normal
                         * page mapping ie lock CPU page table and returns
                         * true.
                         *
                         * For more details on device private memory see HMM
                         * (include/linux/hmm.h or mm/hmm.c).
                         */
                        if (is_swap_pte(*pvmw->pte)) {
                                swp_entry_t entry;

                                /* Handle un-addressable ZONE_DEVICE memory */
                                entry = pte_to_swp_entry(*pvmw->pte);
                                if (!is_device_private_entry(entry))
                                        return false;
                        } else if (!pte_present(*pvmw->pte))
                                return false;
                }
        }
        pvmw->ptl = pte_lockptr(pvmw->vma->vm_mm, pvmw->pmd);
        spin_lock(pvmw->ptl);
        return true;
}

페이지가 매핑되어 있는지 여부를 반환한다. (pvmw->pte 엔트리  —> pvmw->page 매핑, 단 migration 시 swap 엔트리 매핑 여부) 단 sync 모드의 경우 체크하지 않고 항상 true를 반환한다.

  • 코드 라인 3에서 pvmw->pmd 엔트리와 pvmw->address를 사용하여 pte 엔트리를 알아온다.
  • 코드 라인 4에서 PVMW_SYNC 플래그로 요청된 경우에는 다음 엄격한 체크를 하지 않고 true를 반환한다.
  • 코드 라인 5~7에서 PVMW_MIGRATOIN 플래그로 요청된 경우 false를 반환한다. 단 pte 엔트리가 swap_pte에 매핑된 경우는 제외한다.
  • 코드 라인 8~33에서 swap 장치에 매핑된 경우 버디에서 관리하지 않는 HMM 메모리에 swap된 경우가 아니라면 false를 반환한다. 또한 swap 장치에 매핑된 경우가 아니면서 존재하지 않는 페이지에 매핑된 경우 false를 반환한다.
  • 코드 라인 35~37에서 pte 엔트리가 정상 매핑된 경우이다. ptl 락을 획득하고 true를 반환한다.

 

다음 그림은 pwmc->address에 해당하는 pwmc->pte 엔트리가 pwmc->page에 매핑이 되었는지 체크한다.

  • check_pte()와 매우 유사하다. map_pte()에서는 sync 모드를 요청 시 항상 true를 반환한다.

 

page_vma_mapped_walk 구조체

include/linux/rmap.h

struct page_vma_mapped_walk {
        struct page *page;
        struct vm_area_struct *vma;
        unsigned long address;
        pmd_t *pmd;
        pte_t *pte;
        spinlock_t *ptl;
        unsigned int flags;
};
  • *page
    • 매핑된 페이지인지 확인하기 위한 타겟 페이지이다.
  • *vma
    • VMA 영역을 지정한다.
  • address
    • 가상 주소
  • pmd
    • 가상 주소가 매핑된 pmd 엔트리로 함수 내부에서 전달용으로 사용된다.
  • pte
    • 가상 주소가 매핑된 pte 엔트리로 함수 내부에서 전달용으로 사용된다.
  • *ptl
    • PVMW 락으로 함수 내부에서 사용된다.
  • flags
    • PVMW_SYNC
      • 엄격한 체크를 무시한다.
    • PVMW_MIGRATION
      • migration 엔트리를 찾는다.

 

참고

 

댓글 남기기