From: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
To: Andrew Morton <akpm@linux-foundation.org>
Cc: Vlastimil Babka <vbabka@suse.cz>, Jann Horn <jannh@google.com>,
"Liam R . Howlett" <Liam.Howlett@oracle.com>,
Suren Baghdasaryan <surenb@google.com>,
Matthew Wilcox <willy@infradead.org>,
David Hildenbrand <david@redhat.com>,
Pedro Falcato <pfalcato@suse.de>, Rik van Riel <riel@surriel.com>,
Harry Yoo <harry.yoo@oracle.com>, Zi Yan <ziy@nvidia.com>,
Baolin Wang <baolin.wang@linux.alibaba.com>,
Nico Pache <npache@redhat.com>,
Ryan Roberts <ryan.roberts@arm.com>, Dev Jain <dev.jain@arm.com>,
Jakub Matena <matenajakub@gmail.com>,
Wei Yang <richard.weiyang@gmail.com>,
Barry Song <baohua@kernel.org>,
linux-mm@kvack.org, linux-kernel@vger.kernel.org
Subject: [PATCH 03/11] mm/mremap: add MREMAP[_MUST]_RELOCATE_ANON support for large folios
Date: Mon, 9 Jun 2025 14:26:37 +0100 [thread overview]
Message-ID: <43175a21c9e93bc4eb51d73190226411e6b9a7b8.1749473726.git.lorenzo.stoakes@oracle.com> (raw)
In-Reply-To: <cover.1749473726.git.lorenzo.stoakes@oracle.com>
Larger folios are a challenge, as they might be mapped across multiple
VMAs, and can be mapped at a higher page table level (PUD, PMD) or also at
PTE level.
Handle them correctly by checking whether they are fully spanned by the VMA
we are examining. If so, then we can simply relocate the folio as we would
any other.
If not, then we must split the folio. If there is a higher level page table
level mapping the large folio directly then we must also split this.
This will be the minority of cases, and if the operation is performed on a
large, mapping will only be those folios at the start and end of the
mapping which the mapping is not aligned to.
The net result is that we are able to handle large folios mapped in any
form which might be encountered correctly.
Signed-off-by: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
---
mm/mremap.c | 327 +++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 297 insertions(+), 30 deletions(-)
diff --git a/mm/mremap.c b/mm/mremap.c
index 41158aea8c29..d901438ae415 100644
--- a/mm/mremap.c
+++ b/mm/mremap.c
@@ -77,6 +77,7 @@ struct pte_state {
unsigned long new_addr;
unsigned long old_end;
pte_t *ptep;
+ pmd_t *pmdp;
spinlock_t *ptl;
};
@@ -534,40 +535,67 @@ enum pgt_entry {
HPAGE_PUD,
};
-/*
- * Returns an extent of the corresponding size for the pgt_entry specified if
- * valid. Else returns a smaller extent bounded by the end of the source and
- * destination pgt_entry.
- */
-static __always_inline unsigned long get_extent(enum pgt_entry entry,
- struct pagetable_move_control *pmc)
+static void __get_mask_size(enum pgt_entry entry,
+ unsigned long *mask, unsigned long *size)
{
- unsigned long next, extent, mask, size;
- unsigned long old_addr = pmc->old_addr;
- unsigned long old_end = pmc->old_end;
- unsigned long new_addr = pmc->new_addr;
-
switch (entry) {
case HPAGE_PMD:
case NORMAL_PMD:
- mask = PMD_MASK;
- size = PMD_SIZE;
+ *mask = PMD_MASK;
+ *size = PMD_SIZE;
break;
case HPAGE_PUD:
case NORMAL_PUD:
- mask = PUD_MASK;
- size = PUD_SIZE;
+ *mask = PUD_MASK;
+ *size = PUD_SIZE;
break;
default:
BUILD_BUG();
break;
}
+}
+
+/* Same as get extent, only ignores new address. */
+static unsigned long __get_old_extent(struct pagetable_move_control *pmc,
+ unsigned long mask, unsigned long size)
+{
+ unsigned long next, extent;
+ unsigned long old_addr = pmc->old_addr;
+ unsigned long old_end = pmc->old_end;
next = (old_addr + size) & mask;
/* even if next overflowed, extent below will be ok */
extent = next - old_addr;
if (extent > old_end - old_addr)
extent = old_end - old_addr;
+
+ return extent;
+}
+
+static unsigned long get_old_extent(enum pgt_entry entry,
+ struct pagetable_move_control *pmc)
+{
+ unsigned long mask, size;
+
+ __get_mask_size(entry, &mask, &size);
+ return __get_old_extent(pmc, mask, size);
+}
+
+/*
+ * Returns an extent of the corresponding size for the pgt_entry specified if
+ * valid. Else returns a smaller extent bounded by the end of the source and
+ * destination pgt_entry.
+ */
+static __always_inline unsigned long get_extent(enum pgt_entry entry,
+ struct pagetable_move_control *pmc)
+{
+ unsigned long next, extent, mask, size;
+ unsigned long new_addr = pmc->new_addr;
+
+ __get_mask_size(entry, &mask, &size);
+
+ extent = __get_old_extent(pmc, mask, size);
+
next = (new_addr + size) & mask;
if (extent > next - new_addr)
extent = next - new_addr;
@@ -797,6 +825,165 @@ static unsigned long pmc_progress(struct pagetable_move_control *pmc)
return old_addr < orig_old_addr ? 0 : old_addr - orig_old_addr;
}
+/* Assumes folio lock is held. */
+static bool __relocate_large_folio(struct pagetable_move_control *pmc,
+ unsigned long old_addr, unsigned long new_addr,
+ struct folio *folio, bool undo)
+{
+ pgoff_t new_index;
+ struct vm_area_struct *old = pmc->old;
+ struct vm_area_struct *new = pmc->new;
+
+ VM_WARN_ON_ONCE(!folio_test_locked(folio));
+
+ /* no-op. */
+ if (!folio_test_anon(folio))
+ return true;
+
+ if (!undo)
+ new_index = linear_page_index(new, new_addr);
+ else
+ new_index = linear_page_index(old, old_addr);
+
+ /* See comment in relocate_anon_pte(). */
+ folio_move_anon_rmap(folio, undo ? old : new);
+ WRITE_ONCE(folio->index, new_index);
+ return true;
+}
+
+static bool relocate_large_folio(struct pagetable_move_control *pmc,
+ unsigned long old_addr, unsigned long new_addr,
+ struct folio *folio, bool undo)
+{
+ bool ret;
+
+ folio_lock(folio);
+
+ if (!folio_test_large(folio) || folio_test_ksm(folio)) {
+ ret = false;
+ goto out;
+ }
+
+ /* See relocate_anon_pte() for description. */
+ if (WARN_ON_ONCE(folio_maybe_mapped_shared(folio))) {
+ ret = false;
+ goto out;
+ }
+ if (folio_maybe_dma_pinned(folio)) {
+ ret = false;
+ goto out;
+ }
+
+ ret = __relocate_large_folio(pmc, old_addr, new_addr, folio, undo);
+
+out:
+ folio_unlock(folio);
+ return ret;
+}
+
+static bool relocate_anon_pud(struct pagetable_move_control *pmc,
+ pud_t *pudp, bool undo)
+{
+ spinlock_t *ptl;
+ pud_t pud;
+ struct folio *folio;
+ struct page *page;
+ bool ret;
+ unsigned long old_addr = pmc->old_addr;
+ unsigned long new_addr = pmc->new_addr;
+
+ VM_WARN_ON(old_addr & ~HPAGE_PUD_MASK);
+ VM_WARN_ON(new_addr & ~HPAGE_PUD_MASK);
+
+ ptl = pud_trans_huge_lock(pudp, pmc->old);
+ if (!ptl)
+ return false;
+
+ pud = pudp_get(pudp);
+ if (!pud_present(pud)) {
+ ret = true;
+ goto out;
+ }
+ if (!pud_leaf(pud)) {
+ ret = false;
+ goto out;
+ }
+
+ page = pud_page(pud);
+ if (!page) {
+ ret = true;
+ goto out;
+ }
+
+ folio = page_folio(page);
+ ret = relocate_large_folio(pmc, old_addr, new_addr, folio, undo);
+
+out:
+ spin_unlock(ptl);
+ return ret;
+}
+
+static bool relocate_anon_pmd(struct pagetable_move_control *pmc,
+ pmd_t *pmdp, bool undo)
+{
+ spinlock_t *ptl;
+ pmd_t pmd;
+ struct folio *folio;
+ bool ret;
+ unsigned long old_addr = pmc->old_addr;
+ unsigned long new_addr = pmc->new_addr;
+
+ VM_WARN_ON(old_addr & ~HPAGE_PMD_MASK);
+ VM_WARN_ON(new_addr & ~HPAGE_PMD_MASK);
+
+ ptl = pmd_trans_huge_lock(pmdp, pmc->old);
+ if (!ptl)
+ return false;
+
+ pmd = pmdp_get(pmdp);
+ if (!pmd_present(pmd)) {
+ ret = true;
+ goto out;
+ }
+ if (is_huge_zero_pmd(pmd)) {
+ ret = true;
+ goto out;
+ }
+ if (!pmd_leaf(pmd)) {
+ ret = false;
+ goto out;
+ }
+
+ folio = pmd_folio(pmd);
+ if (!folio) {
+ ret = true;
+ goto out;
+ }
+
+ ret = relocate_large_folio(pmc, old_addr, new_addr, folio, undo);
+out:
+ spin_unlock(ptl);
+ return ret;
+}
+
+/*
+ * Is the THP discovered at old_addr fully spanned at both the old and new VMAs?
+ */
+static bool is_thp_fully_spanned(struct pagetable_move_control *pmc,
+ unsigned long old_addr,
+ size_t thp_size)
+{
+ unsigned long old_end = pmc->old_end;
+ unsigned long orig_old_addr = old_end - pmc->len_in;
+ unsigned long aligned_start = old_addr & ~(thp_size - 1);
+ unsigned long aligned_end = aligned_start + thp_size;
+
+ if (aligned_start < orig_old_addr || aligned_end > old_end)
+ return false;
+
+ return true;
+}
+
/*
* If the folio mapped at the specified pte entry can have its index and mapping
* relocated, then do so.
@@ -813,10 +1000,12 @@ static unsigned long relocate_anon_pte(struct pagetable_move_control *pmc,
unsigned long ret = 1;
unsigned long old_addr = state->old_addr;
unsigned long new_addr = state->new_addr;
+ struct mm_struct *mm = current->mm;
old = pmc->old;
new = pmc->new;
+retry:
pte = ptep_get(state->ptep);
/* Ensure we have truly got an anon folio. */
@@ -853,13 +1042,55 @@ static unsigned long relocate_anon_pte(struct pagetable_move_control *pmc,
if (folio_maybe_dma_pinned(folio))
goto out;
- /*
- * This should not happen as we explicitly disallow this, but check
- * anyway.
- */
+ /* If a split huge PMD, try to relocate all at once. */
if (folio_test_large(folio)) {
- ret = 0;
- goto out;
+ size_t size = folio_size(folio);
+
+ if (is_thp_fully_spanned(pmc, old_addr, size) &&
+ __relocate_large_folio(pmc, old_addr, new_addr, folio, undo)) {
+ VM_WARN_ON_ONCE(old_addr & (size - 1));
+ ret = folio_nr_pages(folio);
+ goto out;
+ } else {
+ int err;
+ struct anon_vma *anon_vma = folio_anon_vma(folio);
+
+ /*
+ * If the folio has the anon_vma whose lock we hold, we
+ * have a problem, as split_folio() will attempt to lock
+ * the already-locked anon_vma causing a deadlock. In
+ * this case, bail out.
+ */
+ if (anon_vma->root == pmc->relocate_locked->anon_vma->root) {
+ ret = 0;
+ goto out;
+ }
+
+ /* split_folio() expects elevated refcount. */
+ folio_get(folio);
+
+ /*
+ * We must relinquish/reacquire the PTE lock over this
+ * operation. We hold the folio lock and an increased
+ * reference count, so there's no danger of the folio
+ * disappearing beneath us.
+ */
+ pte_unmap_unlock(state->ptep, state->ptl);
+ err = split_folio(folio);
+ state->ptep = pte_offset_map_lock(mm, state->pmdp,
+ old_addr, &state->ptl);
+ folio_unlock(folio);
+ folio_put(folio);
+
+ if (err || !state->ptep)
+ return 0;
+
+ /*
+ * If we split, we need to look up the folio again, so
+ * simply retry the operation.
+ */
+ goto retry;
+ }
}
if (!undo)
@@ -906,6 +1137,7 @@ static bool relocate_anon_ptes(struct pagetable_move_control *pmc,
.old_addr = pmc->old_addr,
.new_addr = pmc->new_addr,
.old_end = pmc->old_addr + extent,
+ .pmdp = pmdp,
};
pte_t *ptep_start;
bool ret;
@@ -955,29 +1187,64 @@ static bool __relocate_anon_folios(struct pagetable_move_control *pmc, bool undo
pmd_t pmd;
pud_t pud;
- extent = get_extent(NORMAL_PUD, pmc);
+ extent = get_old_extent(NORMAL_PUD, pmc);
pudp = get_old_pud(mm, pmc->old_addr);
if (!pudp)
continue;
pud = pudp_get(pudp);
+ if (pud_trans_huge(pud)) {
+ unsigned long old_addr = pmc->old_addr;
+
+ if (extent != HPAGE_PUD_SIZE)
+ return false;
- if (pud_trans_huge(pud) || pud_devmap(pud))
+ VM_WARN_ON_ONCE(old_addr & ~HPAGE_PUD_MASK);
+
+ /* We may relocate iff the new address is aligned. */
+ if (!(pmc->new_addr & ~HPAGE_PUD_MASK) &&
+ is_thp_fully_spanned(pmc, old_addr, HPAGE_PUD_SIZE)) {
+ if (!relocate_anon_pud(pmc, pudp, undo))
+ return false;
+ continue;
+ }
+
+ /* Otherwise, we split so we can do this with PMDs/PTEs. */
+ split_huge_pud(pmc->old, pudp, old_addr);
+ } else if (pud_devmap(pud)) {
return false;
+ }
- extent = get_extent(NORMAL_PMD, pmc);
+ extent = get_old_extent(NORMAL_PMD, pmc);
pmdp = get_old_pmd(mm, pmc->old_addr);
if (!pmdp)
continue;
pmd = pmdp_get(pmdp);
-
- if (is_swap_pmd(pmd) || pmd_trans_huge(pmd) ||
- pmd_devmap(pmd))
- return false;
-
if (pmd_none(pmd))
continue;
+ if (pmd_trans_huge(pmd)) {
+ unsigned long old_addr = pmc->old_addr;
+
+ if (extent != HPAGE_PMD_SIZE)
+ return false;
+
+ VM_WARN_ON_ONCE(old_addr & ~HPAGE_PMD_MASK);
+
+ /* We may relocate iff the new address is aligned. */
+ if (!(pmc->new_addr & ~HPAGE_PMD_MASK) &&
+ is_thp_fully_spanned(pmc, old_addr, HPAGE_PMD_SIZE)) {
+ if (!relocate_anon_pmd(pmc, pmdp, undo))
+ return false;
+ continue;
+ }
+
+ /* Otherwise, we split so we can do this with PTEs. */
+ split_huge_pmd(pmc->old, pmdp, old_addr);
+ } else if (is_swap_pmd(pmd) || pmd_devmap(pmd)) {
+ return false;
+ }
+
if (!relocate_anon_ptes(pmc, extent, pmdp, undo))
return false;
}
--
2.49.0
next prev parent reply other threads:[~2025-06-09 13:27 UTC|newest]
Thread overview: 41+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-06-09 13:26 [PATCH 00/11] mm/mremap: introduce more mergeable mremap via MREMAP_RELOCATE_ANON Lorenzo Stoakes
2025-06-09 13:26 ` [PATCH 01/11] " Lorenzo Stoakes
2025-06-16 20:58 ` David Hildenbrand
2025-06-17 6:37 ` Harry Yoo
2025-06-17 9:52 ` Lorenzo Stoakes
2025-06-17 10:01 ` David Hildenbrand
2025-06-17 10:07 ` Lorenzo Stoakes
2025-06-17 12:07 ` David Hildenbrand
2025-06-17 11:15 ` Harry Yoo
2025-06-17 11:24 ` Lorenzo Stoakes
2025-06-17 11:49 ` David Hildenbrand
2025-06-17 20:09 ` Lorenzo Stoakes
2025-06-09 13:26 ` [PATCH 02/11] mm/mremap: add MREMAP_MUST_RELOCATE_ANON Lorenzo Stoakes
2025-06-09 13:26 ` Lorenzo Stoakes [this message]
2025-06-09 13:26 ` [PATCH 04/11] tools UAPI: Update copy of linux/mman.h from the kernel sources Lorenzo Stoakes
2025-06-09 13:26 ` [PATCH 05/11] tools/testing/selftests: add sys_mremap() helper to vm_util.h Lorenzo Stoakes
2025-06-09 13:26 ` [PATCH 06/11] tools/testing/selftests: add mremap() cases that merge normally Lorenzo Stoakes
2025-06-09 13:26 ` [PATCH 07/11] tools/testing/selftests: add MREMAP_RELOCATE_ANON merge test cases Lorenzo Stoakes
2025-06-09 13:26 ` [PATCH 08/11] tools/testing/selftests: expand mremap() tests for MREMAP_RELOCATE_ANON Lorenzo Stoakes
2025-06-09 13:26 ` [PATCH 09/11] tools/testing/selftests: have CoW self test use MREMAP_RELOCATE_ANON Lorenzo Stoakes
2025-06-09 13:26 ` [PATCH 10/11] tools/testing/selftests: test relocate anon in split huge page test Lorenzo Stoakes
2025-06-09 13:26 ` [PATCH 11/11] tools/testing/selftests: add MREMAP_RELOCATE_ANON fork tests Lorenzo Stoakes
2025-06-16 20:24 ` [PATCH 00/11] mm/mremap: introduce more mergeable mremap via MREMAP_RELOCATE_ANON David Hildenbrand
2025-06-16 20:41 ` David Hildenbrand
2025-06-17 8:34 ` Pedro Falcato
2025-06-17 8:45 ` David Hildenbrand
2025-06-17 10:57 ` Lorenzo Stoakes
2025-06-17 11:58 ` David Hildenbrand
2025-06-17 12:47 ` Lorenzo Stoakes
2025-06-20 18:59 ` Pedro Falcato
2025-06-20 19:28 ` Lorenzo Stoakes
2025-06-24 9:38 ` David Hildenbrand
2025-06-24 10:19 ` Lorenzo Stoakes
2025-06-24 12:05 ` David Hildenbrand
2025-06-17 10:20 ` Lorenzo Stoakes
2025-06-17 10:50 ` Lorenzo Stoakes
2025-06-17 5:42 ` Lai, Yi
2025-06-17 6:45 ` Harry Yoo
2025-06-17 9:33 ` Lorenzo Stoakes
2025-06-25 15:44 ` Lorenzo Stoakes
2025-06-25 15:58 ` Andrew Morton
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=43175a21c9e93bc4eb51d73190226411e6b9a7b8.1749473726.git.lorenzo.stoakes@oracle.com \
--to=lorenzo.stoakes@oracle.com \
--cc=Liam.Howlett@oracle.com \
--cc=akpm@linux-foundation.org \
--cc=baohua@kernel.org \
--cc=baolin.wang@linux.alibaba.com \
--cc=david@redhat.com \
--cc=dev.jain@arm.com \
--cc=harry.yoo@oracle.com \
--cc=jannh@google.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=matenajakub@gmail.com \
--cc=npache@redhat.com \
--cc=pfalcato@suse.de \
--cc=richard.weiyang@gmail.com \
--cc=riel@surriel.com \
--cc=ryan.roberts@arm.com \
--cc=surenb@google.com \
--cc=vbabka@suse.cz \
--cc=willy@infradead.org \
--cc=ziy@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox