[RESEND v7 22/29] mm/mincore: report PMD swap-cache residency
From: Usama Arif
Date: Mon Sep 14 2026 - 08:45:02 EST
mincore_pte_range() reports every page under a huge PMD as resident. That
is wrong for a swapped-out one: mincore() would tell userspace that memory
it will have to fault in is already in core.
A PMD swap entry is only a compact encoding for HPAGE_PMD_NR slots, so
answer from the swap cache instead. One PMD-sized folio covering the range
makes every page resident exactly when that folio is uptodate; an empty
cache makes none of them; and if the folio was split while the entry stayed
in place, fall back to looking up each covered slot as mincore_swap() does
for a PTE swap entry.
Signed-off-by: Usama Arif <usama.arif@xxxxxxxxx>
---
mm/mincore.c | 47 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 46 insertions(+), 1 deletion(-)
diff --git a/mm/mincore.c b/mm/mincore.c
index c086836bc4bcc..1846c6f68ae63 100644
--- a/mm/mincore.c
+++ b/mm/mincore.c
@@ -85,6 +85,48 @@ static unsigned char mincore_swap(swp_entry_t entry, bool shmem)
return present;
}
+#ifdef CONFIG_THP_SWAP
+static void mincore_pmd_swap(swp_entry_t entry, unsigned long addr,
+ unsigned long end, unsigned char *vec)
+{
+ unsigned long haddr = addr & HPAGE_PMD_MASK;
+ unsigned long start = (addr - haddr) >> PAGE_SHIFT;
+ unsigned long nr = (end - addr) >> PAGE_SHIFT;
+ struct folio *folio;
+ enum swap_pmd_cache state;
+ int i;
+
+ state = swap_pmd_cache_lookup(entry, &folio);
+ if (state == SWAP_PMD_CACHE_HUGE) {
+ memset(vec, folio_test_uptodate(folio), nr);
+ folio_put(folio);
+ return;
+ }
+
+ if (state == SWAP_PMD_CACHE_EMPTY) {
+ memset(vec, 0, nr);
+ return;
+ }
+
+ /*
+ * The PMD swap entry is only a compact encoding for consecutive swap
+ * slots. If the PMD-sized swapcache folio was split, report residency
+ * from the individual slots covered by this mincore() range.
+ */
+ for (i = 0; i < nr; i++)
+ vec[i] = mincore_swap(swp_entry(swp_type(entry),
+ swp_offset(entry) + start + i),
+ false);
+}
+#else /* !CONFIG_THP_SWAP */
+static void mincore_pmd_swap(swp_entry_t entry, unsigned long addr,
+ unsigned long end, unsigned char *vec)
+{
+ /* Nothing produces PMD swap entries without CONFIG_THP_SWAP. */
+ memset(vec, 0, (end - addr) >> PAGE_SHIFT);
+}
+#endif /* CONFIG_THP_SWAP */
+
/*
* Later we can get more picky about what "in core" means precisely.
* For now, simply check to see if the page is in the page cache,
@@ -171,7 +213,10 @@ static int mincore_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end,
ptl = pmd_trans_huge_lock(pmd, vma);
if (ptl) {
- memset(vec, 1, nr);
+ if (pmd_is_swap_entry(*pmd))
+ mincore_pmd_swap(softleaf_from_pmd(*pmd), addr, end, vec);
+ else
+ memset(vec, 1, nr);
spin_unlock(ptl);
goto out;
}
--
2.53.0-Meta