mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 17:47:41 +02:00
mm/khugepaged: generalize __collapse_huge_page_* for mTHP support
generalize the order of the __collapse_huge_page_* and collapse_max_* functions to support future mTHP collapse. The current mechanism for determining collapse with the khugepaged_max_ptes_none value is not designed with mTHP in mind. This raises a key design issue: if we support user defined max_pte_none values (even those scaled by order), a collapse of a lower order can introduces an feedback loop, or "creep", when max_ptes_none is set to a value greater than HPAGE_PMD_NR / 2. [1] With this configuration, a successful collapse to order N will populate enough pages to satisfy the collapse condition on order N+1 on the next scan. This leads to unnecessary work and memory churn. To fix this issue introduce a helper function that will limit mTHP collapse support to two max_ptes_none values, 0 and HPAGE_PMD_NR - 1. This effectively supports two modes: [2] - max_ptes_none=0: never collapses if it encounters an empty PTE or a PTE that maps the shared zeropage. Consequently, no memory bloat. - max_ptes_none=511 (on 4k pagesz): Always collapse to the highest available mTHP order. This removes the possibility of "creep", and a warning will be emitted if any non-supported max_ptes_none value is configured with mTHP enabled. Any intermediate value will default mTHP collapse to max_ptes_none=0. mTHP collapse will not honor the khugepaged_max_ptes_shared or khugepaged_max_ptes_swap parameters, and will fail if it encounters a shared or swapped entry. No functional changes in this patch; however it defines future behavior for mTHP collapse. Link: https://lore.kernel.org/20260605161422.213817-5-npache@redhat.com Link: https://lore.kernel.org/all/e46ab3ab-a3d7-4fb7-9970-d0704bd5d05a@arm.com [1] Link: https://lore.kernel.org/all/37375ace-5601-4d6c-9dac-d1c8268698e9@redhat.com [2] Co-developed-by: Dev Jain <dev.jain@arm.com> Signed-off-by: Dev Jain <dev.jain@arm.com> Signed-off-by: Nico Pache <npache@redhat.com> Reviewed-by: Lorenzo Stoakes <ljs@kernel.org> Acked-by: David Hildenbrand (arm) <david@kernel.org> Reviewed-by: Lance Yang <lance.yang@linux.dev> Reviewed-by: Zi Yan <ziy@nvidia.com> Cc: Alistair Popple <apopple@nvidia.com> Cc: Andrea Arcangeli <aarcange@redhat.com> Cc: Anshuman Khandual <anshuman.khandual@arm.com> Cc: Bagas Sanjaya <bagasdotme@gmail.com> Cc: Baolin Wang <baolin.wang@linux.alibaba.com> Cc: Barry Song <baohua@kernel.org> Cc: Brendan Jackman <jackmanb@google.com> Cc: Byungchul Park <byungchul@sk.com> Cc: Catalin Marinas <catalin.marinas@arm.com> Cc: David Rientjes <rientjes@google.com> Cc: Gregory Price <gourry@gourry.net> Cc: "Huang, Ying" <ying.huang@linux.alibaba.com> Cc: Hugh Dickins <hughd@google.com> Cc: Jan Kara <jack@suse.cz> Cc: Jann Horn <jannh@google.com> Cc: Johannes Weiner <hannes@cmpxchg.org> Cc: Jonathan Corbet <corbet@lwn.net> Cc: Joshua Hahn <joshua.hahnjy@gmail.com> Cc: Kefeng Wang <wangkefeng.wang@huawei.com> Cc: Liam R. Howlett <liam@infradead.org> Cc: "Masami Hiramatsu (Google)" <mhiramat@kernel.org> Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com> Cc: Matthew Brost <matthew.brost@intel.com> Cc: Matthew Wilcox (Oracle) <willy@infradead.org> Cc: Michal Hocko <mhocko@suse.com> Cc: Mike Rapoport <rppt@kernel.org> Cc: Nanyong Sun <sunnanyong@huawei.com> Cc: Pedro Falcato <pfalcato@suse.de> Cc: Peter Xu <peterx@redhat.com> Cc: Rafael Aquini <raquini@redhat.com> Cc: Rakie Kim <rakie.kim@sk.com> Cc: Randy Dunlap <rdunlap@infradead.org> Cc: Ryan Roberts <ryan.roberts@arm.com> Cc: Shivank Garg <shivankg@amd.com> Cc: Steven Rostedt <rostedt@goodmis.org> Cc: Suren Baghdasaryan <surenb@google.com> Cc: Takashi Iwai (SUSE) <tiwai@suse.de> Cc: Thomas Hellström <thomas.hellstrom@linux.intel.com> Cc: Usama Arif <usamaarif642@gmail.com> Cc: Usama Arif <usama.arif@linux.dev> Cc: Vishal Moola (Oracle) <vishal.moola@gmail.com> Cc: Vlastimil Babka <vbabka@suse.cz> Cc: Wei Yang <richard.weiyang@gmail.com> Cc: Will Deacon <will@kernel.org> Cc: Yang Shi <yang@os.amperecomputing.com> Cc: Zach O'Keefe <zokeefe@google.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
This commit is contained in:
parent
45b310faee
commit
5a9c05d866
126
mm/khugepaged.c
126
mm/khugepaged.c
|
|
@ -353,30 +353,51 @@ static bool pte_none_or_zero(pte_t pte)
|
|||
* the shared zeropage for the given collapse operation.
|
||||
* @cc: The collapse control struct
|
||||
* @vma: The vma to check for userfaultfd
|
||||
* @order: The folio order being collapsed to
|
||||
*
|
||||
* Return: Maximum number of empty/shared zeropage PTEs for the collapse operation
|
||||
*/
|
||||
static unsigned int collapse_max_ptes_none(struct collapse_control *cc,
|
||||
struct vm_area_struct *vma)
|
||||
struct vm_area_struct *vma, unsigned int order)
|
||||
{
|
||||
const unsigned int max_ptes_none = khugepaged_max_ptes_none;
|
||||
|
||||
if (vma && userfaultfd_armed(vma))
|
||||
return 0;
|
||||
/* for MADV_COLLAPSE, allow any empty/shared zeropage PTEs */
|
||||
if (!cc->is_khugepaged)
|
||||
return HPAGE_PMD_NR;
|
||||
/* For all other cases respect the user defined maximum */
|
||||
return khugepaged_max_ptes_none;
|
||||
/* for PMD collapse, respect the user defined maximum */
|
||||
if (is_pmd_order(order))
|
||||
return max_ptes_none;
|
||||
/*
|
||||
* for mTHP collapse with the sysctl value set to KHUGEPAGED_MAX_PTES_LIMIT,
|
||||
* scale the maximum number of PTEs to the order of the collapse.
|
||||
*/
|
||||
if (max_ptes_none == KHUGEPAGED_MAX_PTES_LIMIT)
|
||||
return (1 << order) - 1;
|
||||
/*
|
||||
* For mTHP collapse of values other than 0 or KHUGEPAGED_MAX_PTES_LIMIT,
|
||||
* emit a warning and return 0.
|
||||
*/
|
||||
if (max_ptes_none)
|
||||
pr_warn_once("mTHP collapse does not support max_ptes_none"
|
||||
" values other than 0 or %u, defaulting to 0.\n",
|
||||
KHUGEPAGED_MAX_PTES_LIMIT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* collapse_max_ptes_shared - Calculate maximum allowed PTEs that map shared
|
||||
* anonymous pages for the given collapse operation.
|
||||
* @cc: The collapse control struct
|
||||
* @order: The folio order being collapsed to
|
||||
*
|
||||
* Return: Maximum number of PTEs that map shared anonymous pages for the
|
||||
* collapse operation
|
||||
*/
|
||||
static unsigned int collapse_max_ptes_shared(struct collapse_control *cc)
|
||||
static unsigned int collapse_max_ptes_shared(struct collapse_control *cc,
|
||||
unsigned int order)
|
||||
{
|
||||
/*
|
||||
* For MADV_COLLAPSE, do not restrict the number of PTEs that map shared
|
||||
|
|
@ -384,6 +405,13 @@ static unsigned int collapse_max_ptes_shared(struct collapse_control *cc)
|
|||
*/
|
||||
if (!cc->is_khugepaged)
|
||||
return HPAGE_PMD_NR;
|
||||
/*
|
||||
* for mTHP collapse do not allow collapsing anonymous memory pages that
|
||||
* are shared between processes.
|
||||
*/
|
||||
if (!is_pmd_order(order))
|
||||
return 0;
|
||||
/* for PMD collapse, respect the user defined maximum */
|
||||
return khugepaged_max_ptes_shared;
|
||||
}
|
||||
|
||||
|
|
@ -391,11 +419,13 @@ static unsigned int collapse_max_ptes_shared(struct collapse_control *cc)
|
|||
* collapse_max_ptes_swap - Calculate the maximum allowed non-present PTEs or the
|
||||
* maximum allowed non-present pagecache entries for the given collapse operation.
|
||||
* @cc: The collapse control struct
|
||||
* @order: The folio order being collapsed to
|
||||
*
|
||||
* Return: Maximum number of non-present PTEs or the maximum allowed non-present
|
||||
* pagecache entries for the collapse operation.
|
||||
*/
|
||||
static unsigned int collapse_max_ptes_swap(struct collapse_control *cc)
|
||||
static unsigned int collapse_max_ptes_swap(struct collapse_control *cc,
|
||||
unsigned int order)
|
||||
{
|
||||
/*
|
||||
* For MADV_COLLAPSE, do not restrict the number PTEs entries or
|
||||
|
|
@ -403,6 +433,10 @@ static unsigned int collapse_max_ptes_swap(struct collapse_control *cc)
|
|||
*/
|
||||
if (!cc->is_khugepaged)
|
||||
return HPAGE_PMD_NR;
|
||||
/* for mTHP collapse do not allow any non-present PTEs or pagecache entries */
|
||||
if (!is_pmd_order(order))
|
||||
return 0;
|
||||
/* for PMD collapse, respect the user defined maximum */
|
||||
return khugepaged_max_ptes_swap;
|
||||
}
|
||||
|
||||
|
|
@ -599,10 +633,11 @@ static void release_pte_pages(pte_t *pte, pte_t *_pte,
|
|||
|
||||
static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
|
||||
unsigned long start_addr, pte_t *pte, struct collapse_control *cc,
|
||||
struct list_head *compound_pagelist)
|
||||
unsigned int order, struct list_head *compound_pagelist)
|
||||
{
|
||||
const unsigned int max_ptes_none = collapse_max_ptes_none(cc, vma);
|
||||
const unsigned int max_ptes_shared = collapse_max_ptes_shared(cc);
|
||||
const unsigned int max_ptes_none = collapse_max_ptes_none(cc, vma, order);
|
||||
const unsigned int max_ptes_shared = collapse_max_ptes_shared(cc, order);
|
||||
const unsigned long nr_pages = 1UL << order;
|
||||
struct page *page = NULL;
|
||||
struct folio *folio = NULL;
|
||||
unsigned long addr = start_addr;
|
||||
|
|
@ -610,7 +645,7 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
|
|||
int none_or_zero = 0, shared = 0, referenced = 0;
|
||||
enum scan_result result = SCAN_FAIL;
|
||||
|
||||
for (_pte = pte; _pte < pte + HPAGE_PMD_NR;
|
||||
for (_pte = pte; _pte < pte + nr_pages;
|
||||
_pte++, addr += PAGE_SIZE) {
|
||||
pte_t pteval = ptep_get(_pte);
|
||||
if (pte_none_or_zero(pteval)) {
|
||||
|
|
@ -650,6 +685,12 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
|
|||
|
||||
/* See collapse_scan_pmd(). */
|
||||
if (folio_maybe_mapped_shared(folio)) {
|
||||
/*
|
||||
* TODO: Support shared pages without leading to further
|
||||
* mTHP collapses. Currently bringing in new pages via
|
||||
* shared may cause a future higher order collapse on a
|
||||
* rescan of the same range.
|
||||
*/
|
||||
if (++shared > max_ptes_shared) {
|
||||
result = SCAN_EXCEED_SHARED_PTE;
|
||||
count_vm_event(THP_SCAN_EXCEED_SHARED_PTE);
|
||||
|
|
@ -743,18 +784,18 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
|
|||
}
|
||||
|
||||
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 vm_area_struct *vma, unsigned long address,
|
||||
spinlock_t *ptl, unsigned int order,
|
||||
struct list_head *compound_pagelist)
|
||||
{
|
||||
unsigned long end = address + HPAGE_PMD_SIZE;
|
||||
const unsigned long nr_pages = 1UL << order;
|
||||
unsigned long end = address + (PAGE_SIZE * nr_pages);
|
||||
struct folio *src, *tmp;
|
||||
pte_t pteval;
|
||||
pte_t *_pte;
|
||||
unsigned int nr_ptes;
|
||||
|
||||
for (_pte = pte; _pte < pte + HPAGE_PMD_NR; _pte += nr_ptes,
|
||||
for (_pte = pte; _pte < pte + nr_pages; _pte += nr_ptes,
|
||||
address += nr_ptes * PAGE_SIZE) {
|
||||
nr_ptes = 1;
|
||||
pteval = ptep_get(_pte);
|
||||
|
|
@ -807,11 +848,10 @@ static void __collapse_huge_page_copy_succeeded(pte_t *pte,
|
|||
}
|
||||
|
||||
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)
|
||||
pmd_t *pmd, pmd_t orig_pmd, struct vm_area_struct *vma,
|
||||
unsigned int order, struct list_head *compound_pagelist)
|
||||
{
|
||||
const unsigned long nr_pages = 1UL << order;
|
||||
spinlock_t *pmd_ptl;
|
||||
|
||||
/*
|
||||
|
|
@ -827,7 +867,7 @@ static void __collapse_huge_page_copy_failed(pte_t *pte,
|
|||
* Release both raw and compound pages isolated
|
||||
* in __collapse_huge_page_isolate.
|
||||
*/
|
||||
release_pte_pages(pte, pte + HPAGE_PMD_NR, compound_pagelist);
|
||||
release_pte_pages(pte, pte + nr_pages, compound_pagelist);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -847,16 +887,17 @@ static void __collapse_huge_page_copy_failed(pte_t *pte,
|
|||
*/
|
||||
static enum scan_result __collapse_huge_page_copy(pte_t *pte, struct folio *folio,
|
||||
pmd_t *pmd, pmd_t orig_pmd, struct vm_area_struct *vma,
|
||||
unsigned long address, spinlock_t *ptl,
|
||||
unsigned long address, spinlock_t *ptl, unsigned int order,
|
||||
struct list_head *compound_pagelist)
|
||||
{
|
||||
const unsigned long nr_pages = 1UL << order;
|
||||
unsigned int i;
|
||||
enum scan_result result = SCAN_SUCCEED;
|
||||
|
||||
/*
|
||||
* Copying pages' contents is subject to memory poison at any iteration.
|
||||
*/
|
||||
for (i = 0; i < HPAGE_PMD_NR; i++) {
|
||||
for (i = 0; i < nr_pages; i++) {
|
||||
pte_t pteval = ptep_get(pte + i);
|
||||
struct page *page = folio_page(folio, i);
|
||||
unsigned long src_addr = address + i * PAGE_SIZE;
|
||||
|
|
@ -875,10 +916,10 @@ static enum scan_result __collapse_huge_page_copy(pte_t *pte, struct folio *foli
|
|||
|
||||
if (likely(result == SCAN_SUCCEED))
|
||||
__collapse_huge_page_copy_succeeded(pte, vma, address, ptl,
|
||||
compound_pagelist);
|
||||
order, compound_pagelist);
|
||||
else
|
||||
__collapse_huge_page_copy_failed(pte, pmd, orig_pmd, vma,
|
||||
compound_pagelist);
|
||||
order, compound_pagelist);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -1051,16 +1092,20 @@ static enum scan_result check_pmd_still_valid(struct mm_struct *mm,
|
|||
* Bring missing pages in from swap, to complete THP collapse.
|
||||
* Only done if khugepaged_scan_pmd believes it is worthwhile.
|
||||
*
|
||||
* For mTHP orders the function bails on the first swap entry, because
|
||||
* faulting pages back in during collapse could re-populate PTEs that
|
||||
* push a later scan over the threshold for a higher-order collapse.
|
||||
*
|
||||
* Called and returns without pte mapped or spinlocks held.
|
||||
* Returns result: if not SCAN_SUCCEED, mmap_lock has been released.
|
||||
*/
|
||||
static enum scan_result __collapse_huge_page_swapin(struct mm_struct *mm,
|
||||
struct vm_area_struct *vma, unsigned long start_addr, pmd_t *pmd,
|
||||
int referenced)
|
||||
struct vm_area_struct *vma, unsigned long start_addr,
|
||||
pmd_t *pmd, int referenced, unsigned int order)
|
||||
{
|
||||
int swapped_in = 0;
|
||||
vm_fault_t ret = 0;
|
||||
unsigned long addr, end = start_addr + (HPAGE_PMD_NR * PAGE_SIZE);
|
||||
unsigned long addr, end = start_addr + (PAGE_SIZE << order);
|
||||
enum scan_result result;
|
||||
pte_t *pte = NULL;
|
||||
spinlock_t *ptl;
|
||||
|
|
@ -1092,6 +1137,19 @@ static enum scan_result __collapse_huge_page_swapin(struct mm_struct *mm,
|
|||
pte_present(vmf.orig_pte))
|
||||
continue;
|
||||
|
||||
/*
|
||||
* TODO: Support swapin without leading to further mTHP
|
||||
* collapses. Currently bringing in new pages via swapin may
|
||||
* cause a future higher order collapse on a rescan of the same
|
||||
* range.
|
||||
*/
|
||||
if (!is_pmd_order(order)) {
|
||||
pte_unmap(pte);
|
||||
mmap_read_unlock(mm);
|
||||
result = SCAN_EXCEED_SWAP_PTE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
vmf.pte = pte;
|
||||
vmf.ptl = ptl;
|
||||
ret = do_swap_page(&vmf);
|
||||
|
|
@ -1217,7 +1275,7 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long a
|
|||
* that case. Continuing to collapse causes inconsistency.
|
||||
*/
|
||||
result = __collapse_huge_page_swapin(mm, vma, address, pmd,
|
||||
referenced);
|
||||
referenced, HPAGE_PMD_ORDER);
|
||||
if (result != SCAN_SUCCEED)
|
||||
goto out_nolock;
|
||||
}
|
||||
|
|
@ -1265,6 +1323,7 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long a
|
|||
pte = pte_offset_map_lock(mm, &_pmd, address, &pte_ptl);
|
||||
if (pte) {
|
||||
result = __collapse_huge_page_isolate(vma, address, pte, cc,
|
||||
HPAGE_PMD_ORDER,
|
||||
&compound_pagelist);
|
||||
spin_unlock(pte_ptl);
|
||||
} else {
|
||||
|
|
@ -1295,6 +1354,7 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long a
|
|||
|
||||
result = __collapse_huge_page_copy(pte, folio, pmd, _pmd,
|
||||
vma, address, pte_ptl,
|
||||
HPAGE_PMD_ORDER,
|
||||
&compound_pagelist);
|
||||
pte_unmap(pte);
|
||||
if (unlikely(result != SCAN_SUCCEED))
|
||||
|
|
@ -1330,9 +1390,9 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
|
|||
struct vm_area_struct *vma, unsigned long start_addr,
|
||||
bool *lock_dropped, struct collapse_control *cc)
|
||||
{
|
||||
const unsigned int max_ptes_none = collapse_max_ptes_none(cc, vma);
|
||||
const unsigned int max_ptes_shared = collapse_max_ptes_shared(cc);
|
||||
const unsigned int max_ptes_swap = collapse_max_ptes_swap(cc);
|
||||
const unsigned int max_ptes_none = collapse_max_ptes_none(cc, vma, HPAGE_PMD_ORDER);
|
||||
const unsigned int max_ptes_shared = collapse_max_ptes_shared(cc, HPAGE_PMD_ORDER);
|
||||
const unsigned int max_ptes_swap = collapse_max_ptes_swap(cc, HPAGE_PMD_ORDER);
|
||||
pmd_t *pmd;
|
||||
pte_t *pte, *_pte;
|
||||
int none_or_zero = 0, shared = 0, referenced = 0;
|
||||
|
|
@ -2386,8 +2446,8 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
|
|||
unsigned long addr, struct file *file, pgoff_t start,
|
||||
struct collapse_control *cc)
|
||||
{
|
||||
const unsigned int max_ptes_none = collapse_max_ptes_none(cc, NULL);
|
||||
const unsigned int max_ptes_swap = collapse_max_ptes_swap(cc);
|
||||
const unsigned int max_ptes_none = collapse_max_ptes_none(cc, NULL, HPAGE_PMD_ORDER);
|
||||
const unsigned int max_ptes_swap = collapse_max_ptes_swap(cc, HPAGE_PMD_ORDER);
|
||||
struct folio *folio = NULL;
|
||||
struct address_space *mapping = file->f_mapping;
|
||||
XA_STATE(xas, &mapping->i_pages, start);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user