diff options
Diffstat (limited to 'mm/khugepaged.c')
-rw-r--r-- | mm/khugepaged.c | 112 |
1 files changed, 99 insertions, 13 deletions
diff --git a/mm/khugepaged.c b/mm/khugepaged.c index 3b61cd188f7b..c66933d8a8b8 100644 --- a/mm/khugepaged.c +++ b/mm/khugepaged.c @@ -56,6 +56,7 @@ enum scan_result { SCAN_TRUNCATED, SCAN_PAGE_HAS_PRIVATE, SCAN_STORE_FAILED, + SCAN_COPY_MC, }; #define CREATE_TRACE_POINTS @@ -686,20 +687,21 @@ out: return result; } -static void __collapse_huge_page_copy(pte_t *pte, struct page *page, - struct vm_area_struct *vma, - unsigned long address, - spinlock_t *ptl, - struct list_head *compound_pagelist) +static void __collapse_huge_page_copy_succeeded(pte_t *pte, + struct vm_area_struct *vma, + unsigned long address, + spinlock_t *ptl, + struct list_head *compound_pagelist) { - struct page *src_page, *tmp; + struct page *src_page; + struct page *tmp; pte_t *_pte; - for (_pte = pte; _pte < pte + HPAGE_PMD_NR; - _pte++, page++, address += PAGE_SIZE) { - pte_t pteval = *_pte; + pte_t pteval; + for (_pte = pte; _pte < pte + HPAGE_PMD_NR; + _pte++, address += PAGE_SIZE) { + pteval = *_pte; if (pte_none(pteval) || is_zero_pfn(pte_pfn(pteval))) { - clear_user_highpage(page, address); add_mm_counter(vma->vm_mm, MM_ANONPAGES, 1); if (is_zero_pfn(pte_pfn(pteval))) { /* @@ -711,7 +713,6 @@ static void __collapse_huge_page_copy(pte_t *pte, struct page *page, } } else { src_page = pte_page(pteval); - copy_user_highpage(page, src_page, address, vma); if (!PageCompound(src_page)) release_pte_page(src_page); /* @@ -738,6 +739,87 @@ static void __collapse_huge_page_copy(pte_t *pte, struct page *page, } } +static void __collapse_huge_page_copy_failed(pte_t *pte, + pmd_t *pmd, + pmd_t orig_pmd, + struct vm_area_struct *vma, + struct list_head *compound_pagelist) +{ + spinlock_t *pmd_ptl; + + /* + * Re-establish the PMD to point to the original page table + * entry. Restoring PMD needs to be done prior to releasing + * pages. Since pages are still isolated and locked here, + * acquiring anon_vma_lock_write is unnecessary. + */ + pmd_ptl = pmd_lock(vma->vm_mm, pmd); + pmd_populate(vma->vm_mm, pmd, pmd_pgtable(orig_pmd)); + spin_unlock(pmd_ptl); + /* + * Release both raw and compound pages isolated + * in __collapse_huge_page_isolate. + */ + release_pte_pages(pte, pte + HPAGE_PMD_NR, compound_pagelist); +} + +/* + * __collapse_huge_page_copy - attempts to copy memory contents from raw + * pages to a hugepage. Cleans up the raw pages if copying succeeds; + * otherwise restores the original page table and releases isolated raw pages. + * Returns SCAN_SUCCEED if copying succeeds, otherwise returns SCAN_COPY_MC. + * + * @pte: starting of the PTEs to copy from + * @page: the new hugepage to copy contents to + * @pmd: pointer to the new hugepage's PMD + * @orig_pmd: the original raw pages' PMD + * @vma: the original raw pages' virtual memory area + * @address: starting address to copy + * @ptl: lock on raw pages' PTEs + * @compound_pagelist: list that stores compound pages + */ +static int __collapse_huge_page_copy(pte_t *pte, + struct page *page, + pmd_t *pmd, + pmd_t orig_pmd, + struct vm_area_struct *vma, + unsigned long address, + spinlock_t *ptl, + struct list_head *compound_pagelist) +{ + struct page *src_page; + pte_t *_pte; + pte_t pteval; + unsigned long _address; + int result = SCAN_SUCCEED; + + /* + * Copying pages' contents is subject to memory poison at any iteration. + */ + for (_pte = pte, _address = address; _pte < pte + HPAGE_PMD_NR; + _pte++, page++, _address += PAGE_SIZE) { + pteval = *_pte; + if (pte_none(pteval) || is_zero_pfn(pte_pfn(pteval))) { + clear_user_highpage(page, _address); + continue; + } + src_page = pte_page(pteval); + if (copy_mc_user_highpage(page, src_page, _address, vma) > 0) { + result = SCAN_COPY_MC; + break; + } + } + + if (likely(result == SCAN_SUCCEED)) + __collapse_huge_page_copy_succeeded(pte, vma, address, ptl, + compound_pagelist); + else + __collapse_huge_page_copy_failed(pte, pmd, orig_pmd, vma, + compound_pagelist); + + return result; +} + static void khugepaged_alloc_sleep(void) { DEFINE_WAIT(wait); @@ -1111,9 +1193,13 @@ static int collapse_huge_page(struct mm_struct *mm, unsigned long address, */ anon_vma_unlock_write(vma->anon_vma); - __collapse_huge_page_copy(pte, hpage, vma, address, pte_ptl, - &compound_pagelist); + result = __collapse_huge_page_copy(pte, hpage, pmd, _pmd, + vma, address, pte_ptl, + &compound_pagelist); pte_unmap(pte); + if (unlikely(result != SCAN_SUCCEED)) + goto out_up_write; + /* * spin_lock() below is not the equivalent of smp_wmb(), but * the smp_wmb() inside __SetPageUptodate() can be reused to |